[PATCH] spi: bcm2835: Fix buffer overflow with CS able to go beyond limit.

2021-04-20 Thread Joe Burmeister
It was previoulsy possible to have a device tree with more chips than
the driver supports and go off the end of CS arrays.

This patches inforces CS limit but sets that limit to the max of the
default limit and what is in the device tree when driver is loaded.

Signed-off-by: Joe Burmeister 
---
 drivers/spi/spi-bcm2835.c | 77 +--
 1 file changed, 58 insertions(+), 19 deletions(-)

diff --git a/drivers/spi/spi-bcm2835.c b/drivers/spi/spi-bcm2835.c
index aab6c7e5c114..cee761bfffe4 100644
--- a/drivers/spi/spi-bcm2835.c
+++ b/drivers/spi/spi-bcm2835.c
@@ -28,6 +28,7 @@
 #include 
 #include  /* FIXME: using chip internals */
 #include  /* FIXME: using chip internals */
+#include 
 #include 
 #include 
 
@@ -134,7 +135,7 @@ struct bcm2835_spi {
int tx_prologue;
int rx_prologue;
unsigned int tx_spillover;
-   u32 prepare_cs[BCM2835_SPI_NUM_CS];
+   u32 *prepare_cs;
 
struct dentry *debugfs_dir;
u64 count_transfer_polling;
@@ -147,9 +148,9 @@ struct bcm2835_spi {
unsigned int rx_dma_active;
struct dma_async_tx_descriptor *fill_tx_desc;
dma_addr_t fill_tx_addr;
-   struct dma_async_tx_descriptor *clear_rx_desc[BCM2835_SPI_NUM_CS];
+   struct dma_async_tx_descriptor **clear_rx_desc;
dma_addr_t clear_rx_addr;
-   u32 clear_rx_cs[BCM2835_SPI_NUM_CS] cacheline_aligned;
+   u32 *clear_rx_cs;
 };
 
 #if defined(CONFIG_DEBUG_FS)
@@ -875,14 +876,14 @@ static void bcm2835_dma_release(struct spi_controller 
*ctlr,
if (ctlr->dma_rx) {
dmaengine_terminate_sync(ctlr->dma_rx);
 
-   for (i = 0; i < BCM2835_SPI_NUM_CS; i++)
+   for (i = 0; i < ctlr->num_chipselect; i++)
if (bs->clear_rx_desc[i])
dmaengine_desc_free(bs->clear_rx_desc[i]);
 
if (bs->clear_rx_addr)
dma_unmap_single(ctlr->dma_rx->device->dev,
 bs->clear_rx_addr,
-sizeof(bs->clear_rx_cs),
+sizeof(u32) * ctlr->num_chipselect,
 DMA_TO_DEVICE);
 
dma_release_channel(ctlr->dma_rx);
@@ -978,7 +979,7 @@ static int bcm2835_dma_init(struct spi_controller *ctlr, 
struct device *dev,
 
bs->clear_rx_addr = dma_map_single(ctlr->dma_rx->device->dev,
   bs->clear_rx_cs,
-  sizeof(bs->clear_rx_cs),
+  sizeof(u32) * ctlr->num_chipselect,
   DMA_TO_DEVICE);
if (dma_mapping_error(ctlr->dma_rx->device->dev, bs->clear_rx_addr)) {
dev_err(dev, "cannot map clear_rx_cs - not using DMA mode\n");
@@ -987,7 +988,7 @@ static int bcm2835_dma_init(struct spi_controller *ctlr, 
struct device *dev,
goto err_release;
}
 
-   for (i = 0; i < BCM2835_SPI_NUM_CS; i++) {
+   for (i = 0; i < ctlr->num_chipselect; i++) {
bs->clear_rx_desc[i] = dmaengine_prep_dma_cyclic(ctlr->dma_rx,
   bs->clear_rx_addr + i * sizeof(u32),
   sizeof(u32), 0,
@@ -1209,6 +1210,12 @@ static int bcm2835_spi_setup(struct spi_device *spi)
struct gpio_chip *chip;
u32 cs;
 
+   if (spi->chip_select >= ctlr->num_chipselect) {
+   dev_err(>dev, "cs%d >= max %d\n", spi->chip_select,
+   ctlr->num_chipselect);
+   return -EINVAL;
+   }
+
/*
 * Precalculate SPI slave's CS register value for ->prepare_message():
 * The driver always uses software-controlled GPIO chip select, hence
@@ -1233,7 +1240,7 @@ static int bcm2835_spi_setup(struct spi_device *spi)
BCM2835_SPI_CS_CLEAR_RX;
dma_sync_single_for_device(ctlr->dma_rx->device->dev,
   bs->clear_rx_addr,
-  sizeof(bs->clear_rx_cs),
+  sizeof(u32) * ctlr->num_chipselect,
   DMA_TO_DEVICE);
}
 
@@ -1286,39 +1293,71 @@ static int bcm2835_spi_setup(struct spi_device *spi)
return 0;
 }
 
+
+#ifdef CONFIG_OF
+static int bcm2835_spi_get_num_chipselect(struct platform_device *pdev)
+{
+   return max_t(int, of_gpio_named_count(pdev->dev.of_node, "cs-gpios"),
+   BCM2835_SPI_NUM_CS);
+}
+#else
+static int bcm2835_spi_get_num_chipselect(struct platform_device *pdev)
+{
+   return BCM2835_SPI_NUM_CS;
+}
+#endi

[PATCH] spi: Handle SPI device setup callback failure.

2021-04-19 Thread Joe Burmeister
If the setup callback failed, but the controller has auto_runtime_pm
and set_cs, the setup failure could be missed.

Signed-off-by: Joe Burmeister 
---
 drivers/spi/spi.c | 9 -
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index 8b283b2c1668..0c39178f4401 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -3388,8 +3388,15 @@ int spi_setup(struct spi_device *spi)
 
mutex_lock(>controller->io_mutex);
 
-   if (spi->controller->setup)
+   if (spi->controller->setup) {
status = spi->controller->setup(spi);
+   if (status) {
+   mutex_unlock(>controller->io_mutex);
+   dev_err(>controller->dev, "Failed to setup device: 
%d\n",
+   status);
+   return status;
+   }
+   }
 
if (spi->controller->auto_runtime_pm && spi->controller->set_cs) {
status = pm_runtime_get_sync(spi->controller->dev.parent);
-- 
2.30.2



[PATCH] Remove BCM2835 SPI chipselect limit.

2021-04-14 Thread Joe Burmeister
The limit of 4 chipselects for the BCM2835 was not required and also was
not inforced. Without inforcement it was possible to make a device tree
over this limit which would trample memory.

The chipselect count is now obtained from the device tree and expanded
if more devices are added.

Signed-off-by: Joe Burmeister 
---
 drivers/spi/spi-bcm2835.c | 114 +-
 1 file changed, 101 insertions(+), 13 deletions(-)

diff --git a/drivers/spi/spi-bcm2835.c b/drivers/spi/spi-bcm2835.c
index aab6c7e5c114..4f215ec3bd1b 100644
--- a/drivers/spi/spi-bcm2835.c
+++ b/drivers/spi/spi-bcm2835.c
@@ -28,6 +28,7 @@
 #include 
 #include  /* FIXME: using chip internals */
 #include  /* FIXME: using chip internals */
+#include 
 #include 
 #include 
 
@@ -134,7 +135,8 @@ struct bcm2835_spi {
int tx_prologue;
int rx_prologue;
unsigned int tx_spillover;
-   u32 prepare_cs[BCM2835_SPI_NUM_CS];
+   unsigned int allocated_cs_num;
+   u32 *prepare_cs;
 
struct dentry *debugfs_dir;
u64 count_transfer_polling;
@@ -147,9 +149,9 @@ struct bcm2835_spi {
unsigned int rx_dma_active;
struct dma_async_tx_descriptor *fill_tx_desc;
dma_addr_t fill_tx_addr;
-   struct dma_async_tx_descriptor *clear_rx_desc[BCM2835_SPI_NUM_CS];
+   struct dma_async_tx_descriptor **clear_rx_desc;
dma_addr_t clear_rx_addr;
-   u32 clear_rx_cs[BCM2835_SPI_NUM_CS] cacheline_aligned;
+   u32 *clear_rx_cs;
 };
 
 #if defined(CONFIG_DEBUG_FS)
@@ -859,14 +861,18 @@ static void bcm2835_dma_release(struct spi_controller 
*ctlr,
if (ctlr->dma_tx) {
dmaengine_terminate_sync(ctlr->dma_tx);
 
-   if (bs->fill_tx_desc)
+   if (bs->fill_tx_desc) {
dmaengine_desc_free(bs->fill_tx_desc);
+   bs->fill_tx_desc = NULL;
+   }
 
-   if (bs->fill_tx_addr)
+   if (bs->fill_tx_addr) {
dma_unmap_page_attrs(ctlr->dma_tx->device->dev,
 bs->fill_tx_addr, sizeof(u32),
 DMA_TO_DEVICE,
 DMA_ATTR_SKIP_CPU_SYNC);
+   bs->fill_tx_addr = 0;
+   }
 
dma_release_channel(ctlr->dma_tx);
ctlr->dma_tx = NULL;
@@ -875,15 +881,19 @@ static void bcm2835_dma_release(struct spi_controller 
*ctlr,
if (ctlr->dma_rx) {
dmaengine_terminate_sync(ctlr->dma_rx);
 
-   for (i = 0; i < BCM2835_SPI_NUM_CS; i++)
-   if (bs->clear_rx_desc[i])
+   for (i = 0; i < bs->allocated_cs_num; i++)
+   if (bs->clear_rx_desc[i]) {
dmaengine_desc_free(bs->clear_rx_desc[i]);
+   bs->clear_rx_desc[i] = NULL;
+   }
 
-   if (bs->clear_rx_addr)
+   if (bs->clear_rx_addr) {
dma_unmap_single(ctlr->dma_rx->device->dev,
 bs->clear_rx_addr,
-sizeof(bs->clear_rx_cs),
+sizeof(u32) * bs->allocated_cs_num,
 DMA_TO_DEVICE);
+   bs->clear_rx_addr = 0;
+   }
 
dma_release_channel(ctlr->dma_rx);
ctlr->dma_rx = NULL;
@@ -978,7 +988,7 @@ static int bcm2835_dma_init(struct spi_controller *ctlr, 
struct device *dev,
 
bs->clear_rx_addr = dma_map_single(ctlr->dma_rx->device->dev,
   bs->clear_rx_cs,
-  sizeof(bs->clear_rx_cs),
+  sizeof(u32) * bs->allocated_cs_num,
   DMA_TO_DEVICE);
if (dma_mapping_error(ctlr->dma_rx->device->dev, bs->clear_rx_addr)) {
dev_err(dev, "cannot map clear_rx_cs - not using DMA mode\n");
@@ -987,7 +997,7 @@ static int bcm2835_dma_init(struct spi_controller *ctlr, 
struct device *dev,
goto err_release;
}
 
-   for (i = 0; i < BCM2835_SPI_NUM_CS; i++) {
+   for (i = 0; i < bs->allocated_cs_num; i++) {
bs->clear_rx_desc[i] = dmaengine_prep_dma_cyclic(ctlr->dma_rx,
   bs->clear_rx_addr + i * sizeof(u32),
   sizeof(u32), 0,
@@ -1209,6 +1219,48 @@ static int bcm2835_spi_setup(struct spi_device *spi)
struct gpio_chip *chip;
u32 cs;
 
+   if (spi->chip_select >= bs->allocated_cs_num) {
+   unsigned int ne

Re: [PATCH] Add optional chip erase functionality to AT25 EEPROM driver.

2019-08-12 Thread Joe Burmeister

On 12/08/2019 16:51, David Laight wrote:

From: Joe Burmeister

Sent: 09 August 2019 13:54

Many, though not all, AT25s have an instruction for chip erase.
If there is one in the datasheet, it can be added to device tree.
Erase can then be done in userspace via the sysfs API with a new
"erase" device attribute. This matches the eeprom_93xx46 driver's
"erase".

Is it actually worth doing though?

I'm guessing that device erase can easily take over a minute.



That must depend on the AT25. The one we're using (AT25F512A), as it's 
setup, it's fast enough. The datasheet states "The CHIP ERASE cycle time 
typically is 2 seconds.". I've not timed it's because as I said, seamed 
fast enough.



If you can't erase it, then it's basically write once, or you expose it 
with spi_dev to Flashrom to erase it.




When I looked at 'device erase' on an EEPROM it took just as long
as erasing the sectors one at a time - but without the warm cosy
feeling that progress was being made.



I didn't look at sector erase as I'm only interested in erasing it all 
and there was a command for it. I figured if someone wanted sector by 
sector they would implement it.





Not only that you can't really interrupt the erase, so either
the application has to sleep uninterruptibly for the duration
or you have to have some kind of 'device busy' response while
it is done asynchronously.



That's true, but as I said, it's fast enough it's not an issue for us.


David

-
Registered Address Lakeside, Bramley Road, Mount Farm, Milton Keynes, MK1 1PT, 
UK
Registration No: 1397386 (Wales)


Regards,

Joe



Re: [PATCH] Add optional chip erase functionality to AT25 EEPROM driver.

2019-08-09 Thread Joe Burmeister

Hi Mark,


On 09/08/2019 14:54, Mark Rutland wrote:

On Fri, Aug 09, 2019 at 01:53:55PM +0100, Joe Burmeister wrote:

Many, though not all, AT25s have an instruction for chip erase.
If there is one in the datasheet, it can be added to device tree.
Erase can then be done in userspace via the sysfs API with a new
"erase" device attribute. This matches the eeprom_93xx46 driver's
"erase".

Signed-off-by: Joe Burmeister 
---
  .../devicetree/bindings/eeprom/at25.txt   |  2 +
  drivers/misc/eeprom/at25.c| 83 ++-
  2 files changed, 82 insertions(+), 3 deletions(-)

diff --git a/Documentation/devicetree/bindings/eeprom/at25.txt 
b/Documentation/devicetree/bindings/eeprom/at25.txt
index b3bde97dc199..c65d11e14c7a 100644
--- a/Documentation/devicetree/bindings/eeprom/at25.txt
+++ b/Documentation/devicetree/bindings/eeprom/at25.txt
@@ -19,6 +19,7 @@ Optional properties:
  - spi-cpha : SPI shifted clock phase, as per spi-bus bindings.
  - spi-cpol : SPI inverse clock polarity, as per spi-bus bindings.
  - read-only : this parameter-less property disables writes to the eeprom
+- chip_erase_instruction : Chip erase instruction for this AT25, often 0xc7 or 
0x62.

This should be using '-' rather than '_', as per general DT conventions
and as with the existing properties.


  Obsolete legacy properties can be used in place of "size", "pagesize",
  "address-width", and "read-only":
@@ -39,4 +40,5 @@ Example:
pagesize = <64>;
size = <32768>;
address-width = <16>;
+   chip_erase_instruction = <0x62>;

[...]


+   /* Optional chip erase instruction */
+   device_property_read_u8(>dev, "chip_erase_instruction", 
>erase_instr);

This will not behave as you expect, since you didn't mark the property as
8-bits.


Rats, I forgot to update the documentation. In my device tree I have 
already found the /bit 8/ bit.



Read this as a u32 into the existing val temporary variable, as is done
for pagesize. You can add a warnign if it's out-of-range.


32bit would certainly read better in the device tree. I'll do that.


Thanks,
Mark.



Cheers,


Joe





Re: [PATCH] Add optional chip erase functionality to AT25 EEPROM driver.

2019-08-09 Thread Joe Burmeister

Hi Greg,

On 09/08/2019 14:00, Greg Kroah-Hartman wrote:

On Fri, Aug 09, 2019 at 01:53:55PM +0100, Joe Burmeister wrote:

+static void _eeprom_at25_store_erase_locked(struct at25_data *at25)
+{
+   unsigned long   timeout, retries;
+   int sr, status;
+   u8  cp;
+
+   cp = AT25_WREN;
+   status = spi_write(at25->spi, , 1);
+   if (status < 0) {
+   dev_dbg(>spi->dev, "ERASE WREN --> %d\n", status);
+   return;
+   }
+   cp = at25->erase_instr;
+   status = spi_write(at25->spi, , 1);
+   if (status < 0) {
+   dev_dbg(>spi->dev, "CHIP_ERASE --> %d\n", status);
+   return;
+   }
+   /* Wait for non-busy status */
+   timeout = jiffies + msecs_to_jiffies(ERASE_TIMEOUT);
+   retries = 0;
+   do {
+   sr = spi_w8r8(at25->spi, AT25_RDSR);
+   if (sr < 0 || (sr & AT25_SR_nRDY)) {
+   dev_dbg(>spi->dev,
+   "rdsr --> %d (%02x)\n", sr, sr);
+   /* at HZ=100, this is slw */
+   msleep(1);
+   continue;
+   }
+   if (!(sr & AT25_SR_nRDY))
+   return;
+   } while (retries++ < 200 || time_before_eq(jiffies, timeout));
+
+   if ((sr < 0) || (sr & AT25_SR_nRDY)) {
+   dev_err(>spi->dev,
+   "chip erase, timeout after %u msecs\n",
+   jiffies_to_msecs(jiffies -
+   (timeout - ERASE_TIMEOUT)));
+   status = -ETIMEDOUT;
+   return;
+   }
+}
+
+

No need for 2 lines :(


Sorry, other coding conventions I'm used to.



+static ssize_t eeprom_at25_store_erase(struct device *dev,
+struct device_attribute *attr,
+const char *buf, size_t count)
+{
+   struct at25_data *at25 = dev_get_drvdata(dev);
+   int erase = 0;
+
+   sscanf(buf, "%d", );
+   if (erase) {
+   mutex_lock(>lock);
+   _eeprom_at25_store_erase_locked(at25);
+   mutex_unlock(>lock);
+   }
+
+   return count;
+}
+
+static DEVICE_ATTR(erase, S_IWUSR, NULL, eeprom_at25_store_erase);
+
+

Same here.

Also, where is the Documentation/ABI/ update for the new sysfs file?


There isn't anything for the existing SPI EEPROM stuff I can see.

Would I have to document what was already there to add my bit?



  static int at25_probe(struct spi_device *spi)
  {
struct at25_data*at25 = NULL;
@@ -311,6 +379,7 @@ static int at25_probe(struct spi_device *spi)
int err;
int sr;
int addrlen;
+   int has_erase;
  
  	/* Chip description */

if (!spi->dev.platform_data) {
@@ -352,6 +421,9 @@ static int at25_probe(struct spi_device *spi)
spi_set_drvdata(spi, at25);
at25->addrlen = addrlen;
  
+	/* Optional chip erase instruction */

+   device_property_read_u8(>dev, "chip_erase_instruction", 
>erase_instr);
+
at25->nvmem_config.name = dev_name(>dev);
at25->nvmem_config.dev = >dev;
at25->nvmem_config.read_only = chip.flags & EE_READONLY;
@@ -370,17 +442,22 @@ static int at25_probe(struct spi_device *spi)
if (IS_ERR(at25->nvmem))
return PTR_ERR(at25->nvmem);
  
-	dev_info(>dev, "%d %s %s eeprom%s, pagesize %u\n",

+   has_erase = (!(chip.flags & EE_READONLY) && at25->erase_instr);
+
+   dev_info(>dev, "%d %s %s eeprom%s, pagesize %u%s\n",
(chip.byte_len < 1024) ? chip.byte_len : (chip.byte_len / 1024),
(chip.byte_len < 1024) ? "Byte" : "KByte",
at25->chip.name,
(chip.flags & EE_READONLY) ? " (readonly)" : "",
-   at25->chip.page_size);
+   at25->chip.page_size, (has_erase)?" ":"");
+
+   if (has_erase && device_create_file(>dev, _attr_erase))
+   dev_err(>dev, "can't create erase interface\n");

You just raced with userspace and lost :(

Please create an attribute group and add it to the .dev_groups pointer
in struct driver so it can be created properly in a race-free manner.

See the thread at:

https://lore.kernel.org/r/20190731124349.4474-2-gre...@linuxfoundation.org
for the details on how to do that.


Clearly I didn't know about that. I'll do some reading when I've got a 
bit of time and try a again.




thanks,

greg k-h


Cheers,

Joe



[PATCH] Add optional chip erase functionality to AT25 EEPROM driver.

2019-08-09 Thread Joe Burmeister
Many, though not all, AT25s have an instruction for chip erase.
If there is one in the datasheet, it can be added to device tree.
Erase can then be done in userspace via the sysfs API with a new
"erase" device attribute. This matches the eeprom_93xx46 driver's
"erase".

Signed-off-by: Joe Burmeister 
---
 .../devicetree/bindings/eeprom/at25.txt   |  2 +
 drivers/misc/eeprom/at25.c| 83 ++-
 2 files changed, 82 insertions(+), 3 deletions(-)

diff --git a/Documentation/devicetree/bindings/eeprom/at25.txt 
b/Documentation/devicetree/bindings/eeprom/at25.txt
index b3bde97dc199..c65d11e14c7a 100644
--- a/Documentation/devicetree/bindings/eeprom/at25.txt
+++ b/Documentation/devicetree/bindings/eeprom/at25.txt
@@ -19,6 +19,7 @@ Optional properties:
 - spi-cpha : SPI shifted clock phase, as per spi-bus bindings.
 - spi-cpol : SPI inverse clock polarity, as per spi-bus bindings.
 - read-only : this parameter-less property disables writes to the eeprom
+- chip_erase_instruction : Chip erase instruction for this AT25, often 0xc7 or 
0x62.
 
 Obsolete legacy properties can be used in place of "size", "pagesize",
 "address-width", and "read-only":
@@ -39,4 +40,5 @@ Example:
pagesize = <64>;
size = <32768>;
address-width = <16>;
+   chip_erase_instruction = <0x62>;
};
diff --git a/drivers/misc/eeprom/at25.c b/drivers/misc/eeprom/at25.c
index 99de6939cd5a..28141bc4028f 100644
--- a/drivers/misc/eeprom/at25.c
+++ b/drivers/misc/eeprom/at25.c
@@ -35,6 +35,7 @@ struct at25_data {
unsignedaddrlen;
struct nvmem_config nvmem_config;
struct nvmem_device *nvmem;
+   u8  erase_instr;
 };
 
 #defineAT25_WREN   0x06/* latch the write enable */
@@ -59,6 +60,8 @@ struct at25_data {
  */
 #defineEE_TIMEOUT  25
 
+#define ERASE_TIMEOUT 2020
+
 /*-*/
 
 #defineio_limitPAGE_SIZE   /* bytes */
@@ -304,6 +307,71 @@ static int at25_fw_to_chip(struct device *dev, struct 
spi_eeprom *chip)
return 0;
 }
 
+static void _eeprom_at25_store_erase_locked(struct at25_data *at25)
+{
+   unsigned long   timeout, retries;
+   int sr, status;
+   u8  cp;
+
+   cp = AT25_WREN;
+   status = spi_write(at25->spi, , 1);
+   if (status < 0) {
+   dev_dbg(>spi->dev, "ERASE WREN --> %d\n", status);
+   return;
+   }
+   cp = at25->erase_instr;
+   status = spi_write(at25->spi, , 1);
+   if (status < 0) {
+   dev_dbg(>spi->dev, "CHIP_ERASE --> %d\n", status);
+   return;
+   }
+   /* Wait for non-busy status */
+   timeout = jiffies + msecs_to_jiffies(ERASE_TIMEOUT);
+   retries = 0;
+   do {
+   sr = spi_w8r8(at25->spi, AT25_RDSR);
+   if (sr < 0 || (sr & AT25_SR_nRDY)) {
+   dev_dbg(>spi->dev,
+   "rdsr --> %d (%02x)\n", sr, sr);
+   /* at HZ=100, this is slw */
+   msleep(1);
+   continue;
+   }
+   if (!(sr & AT25_SR_nRDY))
+   return;
+   } while (retries++ < 200 || time_before_eq(jiffies, timeout));
+
+   if ((sr < 0) || (sr & AT25_SR_nRDY)) {
+   dev_err(>spi->dev,
+   "chip erase, timeout after %u msecs\n",
+   jiffies_to_msecs(jiffies -
+   (timeout - ERASE_TIMEOUT)));
+   status = -ETIMEDOUT;
+   return;
+   }
+}
+
+
+static ssize_t eeprom_at25_store_erase(struct device *dev,
+struct device_attribute *attr,
+const char *buf, size_t count)
+{
+   struct at25_data *at25 = dev_get_drvdata(dev);
+   int erase = 0;
+
+   sscanf(buf, "%d", );
+   if (erase) {
+   mutex_lock(>lock);
+   _eeprom_at25_store_erase_locked(at25);
+   mutex_unlock(>lock);
+   }
+
+   return count;
+}
+
+static DEVICE_ATTR(erase, S_IWUSR, NULL, eeprom_at25_store_erase);
+
+
 static int at25_probe(struct spi_device *spi)
 {
struct at25_data*at25 = NULL;
@@ -311,6 +379,7 @@ static int at25_probe(struct spi_device *spi)
int err;
int sr;
int addrlen;
+   int has_erase;
 
/* Chip description */
if (!spi->dev.platform_data) {
@@ -352,6 +421,9 @@ static in

[PATCH] Add erase interface for AT25 driver.

2019-08-05 Thread Joe Burmeister
Signed-off-by: Joe Burmeister 
---
 drivers/misc/eeprom/at25.c | 75 +-
 1 file changed, 74 insertions(+), 1 deletion(-)

diff --git a/drivers/misc/eeprom/at25.c b/drivers/misc/eeprom/at25.c
index 99de6939cd5a..b1fb3bfd935d 100644
--- a/drivers/misc/eeprom/at25.c
+++ b/drivers/misc/eeprom/at25.c
@@ -43,6 +43,7 @@ struct at25_data {
 #defineAT25_WRSR   0x01/* write status register */
 #defineAT25_READ   0x03/* read byte(s) */
 #defineAT25_WRITE  0x02/* write byte(s)/sector */
+#define AT25_CHIP_ERASE0x62/* Erase whole chip */
 
 #defineAT25_SR_nRDY0x01/* nRDY = write-in-progress */
 #defineAT25_SR_WEN 0x02/* write enable (latched) */
@@ -52,6 +53,7 @@ struct at25_data {
 
 #defineAT25_INSTR_BIT3 0x08/* Additional address bit in 
instr */
 
+
 #define EE_MAXADDRLEN  3   /* 24 bit addresses, up to 2 MBytes */
 
 /* Specs often allow 5 msec for a page write, sometimes 20 msec;
@@ -59,6 +61,8 @@ struct at25_data {
  */
 #defineEE_TIMEOUT  25
 
+#define ERASE_TIMEOUT 2020
+
 /*-*/
 
 #defineio_limitPAGE_SIZE   /* bytes */
@@ -304,6 +308,71 @@ static int at25_fw_to_chip(struct device *dev, struct 
spi_eeprom *chip)
return 0;
 }
 
+static void _eeprom_at25_store_erase_locked(struct at25_data *at25)
+{
+   unsigned long   timeout, retries;
+   int sr, status;
+   u8  cp;
+
+   cp = AT25_WREN;
+   status = spi_write(at25->spi, , 1);
+   if (status < 0) {
+   dev_dbg(>spi->dev, "ERASE WREN --> %d\n", status);
+   return;
+   }
+   cp = AT25_CHIP_ERASE;
+   status = spi_write(at25->spi, , 1);
+   if (status < 0) {
+   dev_dbg(>spi->dev, "CHIP_ERASE --> %d\n", status);
+   return;
+   }
+   /* Wait for non-busy status */
+   timeout = jiffies + msecs_to_jiffies(ERASE_TIMEOUT);
+   retries = 0;
+   do {
+   sr = spi_w8r8(at25->spi, AT25_RDSR);
+   if (sr < 0 || (sr & AT25_SR_nRDY)) {
+   dev_dbg(>spi->dev,
+   "rdsr --> %d (%02x)\n", sr, sr);
+   /* at HZ=100, this is slw */
+   msleep(1);
+   continue;
+   }
+   if (!(sr & AT25_SR_nRDY))
+   return;
+   } while (retries++ < 200 || time_before_eq(jiffies, timeout));
+
+   if ((sr < 0) || (sr & AT25_SR_nRDY)) {
+   dev_err(>spi->dev,
+   "chip erase, timeout after %u msecs\n",
+   jiffies_to_msecs(jiffies -
+   (timeout - ERASE_TIMEOUT)));
+   status = -ETIMEDOUT;
+   return;
+   }
+}
+
+
+static ssize_t eeprom_at25_store_erase(struct device *dev,
+struct device_attribute *attr,
+const char *buf, size_t count)
+{
+   struct at25_data *at25 = dev_get_drvdata(dev);
+   int erase = 0;
+
+   sscanf(buf, "%d", );
+   if (erase) {
+   mutex_lock(>lock);
+   _eeprom_at25_store_erase_locked(at25);
+   mutex_unlock(>lock);
+   }
+
+   return count;
+}
+
+static DEVICE_ATTR(erase, S_IWUSR, NULL, eeprom_at25_store_erase);
+
+
 static int at25_probe(struct spi_device *spi)
 {
struct at25_data*at25 = NULL;
@@ -376,11 +445,15 @@ static int at25_probe(struct spi_device *spi)
at25->chip.name,
(chip.flags & EE_READONLY) ? " (readonly)" : "",
at25->chip.page_size);
+
+if (!(chip.flags & EE_READONLY))
+if (device_create_file(>dev, _attr_erase))
+dev_err(>dev, "can't create erase interface\n");
+
return 0;
 }
 
 /*-*/
-
 static const struct of_device_id at25_of_match[] = {
{ .compatible = "atmel,at25", },
{ }
-- 
2.20.1