* [PATCH] SST25L (non JEDEC) SPI Flash driver
@ 2009-06-22 3:58 Ryan Mallon
2009-06-22 8:15 ` Artem Bityutskiy
2009-06-22 8:22 ` Linus Walleij
0 siblings, 2 replies; 14+ messages in thread
From: Ryan Mallon @ 2009-06-22 3:58 UTC (permalink / raw)
To: David Woodhouse, linux-mtd, spi-devel-general, mike, linux kernel
Add support for the non JEDEC SST25L SPI Flash devices.
Signed-off-by: Andre Renaud <andre@bluewatersys.com>
Signed-off-by: Ryan Mallon <ryan@bluewatersys.com>
---
diff --git a/drivers/mtd/devices/Kconfig b/drivers/mtd/devices/Kconfig
index 325fab9..c222514 100644
--- a/drivers/mtd/devices/Kconfig
+++ b/drivers/mtd/devices/Kconfig
@@ -104,6 +104,16 @@ config M25PXX_USE_FAST_READ
help
This option enables FAST_READ access supported by ST M25Pxx.
+config MTD_SST25L
+ tristate "Support SST25L (non JEDEC) SPI Flash chips"
+ depends on SPI_MASTER
+ help
+ This enables access to the non JEDEC SST25L SPI flash chips, used
+ for program and data storage.
+
+ Set up your spi devices with the right board-specific platform data,
+ if you want to specify device partitioning.
+
config MTD_SLRAM
tristate "Uncached system RAM"
help
diff --git a/drivers/mtd/devices/Makefile b/drivers/mtd/devices/Makefile
index 0993d5c..ab5c9b9 100644
--- a/drivers/mtd/devices/Makefile
+++ b/drivers/mtd/devices/Makefile
@@ -16,3 +16,4 @@ obj-$(CONFIG_MTD_LART) += lart.o
obj-$(CONFIG_MTD_BLOCK2MTD) += block2mtd.o
obj-$(CONFIG_MTD_DATAFLASH) += mtd_dataflash.o
obj-$(CONFIG_MTD_M25P80) += m25p80.o
+obj-$(CONFIG_MTD_SST25L) += sst25l.o
diff --git a/drivers/mtd/devices/sst25l.c b/drivers/mtd/devices/sst25l.c
new file mode 100644
index 0000000..8e62479
--- /dev/null
+++ b/drivers/mtd/devices/sst25l.c
@@ -0,0 +1,508 @@
+/*
+ * drivers/mtd/sst25l.c
+ *
+ * Driver for SST25L SPI Flash chips
+ *
+ * Copyright (C) 2009 Bluewater Systems Ltd
+ * Author: Andre Renaud <andre@bluewatersys.com>
+ * Author: Ryan Mallon <ryan@bluewatersys.com>
+ *
+ * Based on m25p80.c
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/mutex.h>
+#include <linux/math64.h>
+#include <linux/interrupt.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+
+#include <linux/spi/spi.h>
+#include <linux/spi/flash.h>
+
+/* Erases can take up to 3 seconds! */
+#define MAX_READY_WAIT_JIFFIES msecs_to_jiffies(3000)
+
+#define SST25L_CMD_WRSR 0x01 /* Write status register */
+#define SST25L_CMD_WRDI 0x04 /* Write disable */
+#define SST25L_CMD_RDSR 0x05 /* Read status register */
+#define SST25L_CMD_WREN 0x06 /* Write enable */
+#define SST25L_CMD_READ 0x03 /* High speed read */
+
+#define SST25L_CMD_EWSR 0x50 /* Enable write status register */
+#define SST25L_CMD_BLOCK_ERASE 0x52 /* Erase sector */
+#define SST25L_CMD_READ_ID 0x90 /* Read device ID */
+#define SST25L_CMD_AAI_PROGRAM 0xaf /* Auto address increment */
+
+#define SST25L_STATUS_BUSY (1 << 0) /* Chip is busy */
+#define SST25L_STATUS_WREN (1 << 1) /* Write enabled */
+#define SST25L_STATUS_BP0 (1 << 2) /* Block protection 0 */
+#define SST25L_STATUS_BP1 (1 << 3) /* Block protection 1 */
+
+struct sst25l_flash {
+ struct spi_device *spi;
+ struct mutex lock;
+ struct mtd_info mtd;
+
+ int partitioned;
+};
+
+struct flash_info {
+ const char *name;
+ u16 device_id;
+ unsigned page_size;
+ unsigned nr_pages;
+ unsigned erase_size;
+};
+
+#define to_sst25l_flash(x) container_of(x, struct sst25l_flash, mtd)
+
+static struct flash_info __devinitdata sst25l_flash_info[] = {
+ {"sst25lf020a", 0xbf43, 256, 1024, 32 * 1024},
+ {"sst25lf040a", 0xbf44, 256, 2048, 32 * 1024},
+};
+
+static int sst25l_status(struct sst25l_flash *flash, int *status)
+{
+ u8 command, response;
+ int err;
+
+ command = SST25L_CMD_RDSR;
+ err = spi_write_then_read(flash->spi, &command, 1, &response, 1);
+ if (err < 0)
+ return err;
+
+ *status = response;
+ return 0;
+}
+
+static int sst25l_write_enable(struct sst25l_flash *flash, int enable)
+{
+ u8 command[2];
+ int status, err;
+
+ command[0] = enable ? SST25L_CMD_WREN : SST25L_CMD_WRDI;
+ err = spi_write(flash->spi, command, 1);
+ if (err)
+ return err;
+
+ command[0] = SST25L_CMD_EWSR;
+ err = spi_write(flash->spi, command, 1);
+ if (err)
+ return err;
+
+ command[0] = SST25L_CMD_WRSR;
+ command[1] = enable ? 0 : SST25L_STATUS_BP0 | SST25L_STATUS_BP1;
+ err = spi_write(flash->spi, command, 2);
+ if (err)
+ return err;
+
+ if (enable) {
+ err = sst25l_status(flash, &status);
+ if (err)
+ return err;
+ if (!(status & SST25L_STATUS_WREN))
+ return -EROFS;
+ }
+
+ return 0;
+}
+
+static int sst25l_wait_till_ready(struct sst25l_flash *flash)
+{
+ unsigned long deadline;
+ int status, err;
+
+ deadline = jiffies + MAX_READY_WAIT_JIFFIES;
+ do {
+ err = sst25l_status(flash, &status);
+ if (err)
+ return err;
+ if (!(status & SST25L_STATUS_BUSY))
+ return 0;
+
+ cond_resched();
+ } while (!time_after_eq(jiffies, deadline));
+
+ return -ETIMEDOUT;
+}
+
+static int sst25l_erase_block(struct sst25l_flash *flash, u32 offset)
+{
+ u8 command[4];
+ int err;
+
+ err = sst25l_write_enable(flash, 1);
+ if (err)
+ return err;
+
+ command[0] = SST25L_CMD_BLOCK_ERASE;
+ command[1] = offset >> 16;
+ command[2] = offset >> 8;
+ command[3] = offset;
+ err = spi_write(flash->spi, command, 4);
+ if (err)
+ return err;
+
+ err = sst25l_wait_till_ready(flash);
+ if (err)
+ return err;
+
+ return sst25l_write_enable(flash, 0);
+}
+
+static int sst25l_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+ struct sst25l_flash *flash = to_sst25l_flash(mtd);
+ u32 addr, end, rem;
+ int err;
+
+ /* Sanity checks */
+ if (instr->addr + instr->len > flash->mtd.size)
+ return -EINVAL;
+
+ div_u64_rem(instr->len, mtd->erasesize, &rem);
+ if (rem)
+ return -EINVAL;
+
+ div_u64_rem(instr->addr, mtd->erasesize, &rem);
+ if (rem)
+ return -EINVAL;
+
+ addr = instr->addr;
+ end = addr + instr->len;
+
+ mutex_lock(&flash->lock);
+
+ err = sst25l_wait_till_ready(flash);
+ if (err)
+ return err;
+
+ while (addr < end) {
+ err = sst25l_erase_block(flash, addr);
+ if (err) {
+ mutex_unlock(&flash->lock);
+ instr->state = MTD_ERASE_FAILED;
+ dev_err(&flash->spi->dev, "Erase failed\n");
+ return err;
+ }
+
+ addr += mtd->erasesize;
+ }
+
+ mutex_unlock(&flash->lock);
+
+ instr->state = MTD_ERASE_DONE;
+ mtd_erase_callback(instr);
+ return 0;
+}
+
+static int sst25l_read(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ struct sst25l_flash *flash = to_sst25l_flash(mtd);
+ struct spi_transfer transfer[2];
+ struct spi_message message;
+ u8 command[4];
+ int ret;
+
+ /* Sanity checking */
+ if (len == 0)
+ return 0;
+
+ if (from + len > flash->mtd.size)
+ return -EINVAL;
+
+ if (retlen)
+ *retlen = 0;
+
+ spi_message_init(&message);
+ memset(&transfer, 0, sizeof(transfer));
+
+ command[0] = SST25L_CMD_READ;
+ command[1] = from >> 16;
+ command[2] = from >> 8;
+ command[3] = from;
+
+ transfer[0].tx_buf = command;
+ transfer[0].len = sizeof(command);
+ spi_message_add_tail(&transfer[0], &message);
+
+ transfer[1].rx_buf = buf;
+ transfer[1].len = len;
+ spi_message_add_tail(&transfer[1], &message);
+
+ mutex_lock(&flash->lock);
+
+ /* Wait for previous write/erase to complete */
+ ret = sst25l_wait_till_ready(flash);
+ if (ret) {
+ mutex_unlock(&flash->lock);
+ return ret;
+ }
+
+ spi_sync(flash->spi, &message);
+
+ if (retlen && message.actual_length > sizeof(command))
+ *retlen += message.actual_length - sizeof(command);
+
+ mutex_unlock(&flash->lock);
+ return 0;
+}
+
+static int sst25l_write(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf)
+{
+ struct sst25l_flash *flash = to_sst25l_flash(mtd);
+ int i, j, ret, bytes, copied = 0;
+ u8 command[5];
+ u32 rem;
+
+ /* Sanity checks */
+ if (!len)
+ return 0;
+
+ if (to + len > flash->mtd.size)
+ return -EINVAL;
+
+ div_u64_rem(to, mtd->writesize, &rem);
+ if (rem)
+ return -EINVAL;
+
+ mutex_lock(&flash->lock);
+
+ ret = sst25l_write_enable(flash, 1);
+ if (ret)
+ goto out;
+
+ for (i = 0; i < len; i += mtd->writesize) {
+ ret = sst25l_wait_till_ready(flash);
+ if (ret)
+ goto out;
+
+ /* Write the first byte of the page */
+ command[0] = SST25L_CMD_AAI_PROGRAM;
+ command[1] = (to + i) >> 16;
+ command[2] = (to + i) >> 8;
+ command[3] = (to + i);
+ command[4] = buf[i];
+ ret = spi_write(flash->spi, command, 5);
+ if (ret < 0)
+ goto out;
+ copied++;
+
+ /*
+ * Write the remaining bytes using auto address
+ * increment mode
+ */
+ bytes = min(mtd->writesize, len - i);
+ for (j = 1; j < bytes; j++, copied++) {
+ ret = sst25l_wait_till_ready(flash);
+ if (ret)
+ goto out;
+
+ command[1] = buf[i + j];
+ ret = spi_write(flash->spi, command, 2);
+ if (ret)
+ goto out;
+ }
+ }
+
+out:
+ ret = sst25l_write_enable(flash, 0);
+
+ if (retlen)
+ *retlen = copied;
+
+ mutex_unlock(&flash->lock);
+ return ret;
+}
+
+static struct flash_info *__devinit sst25l_match_device(struct spi_device *spi)
+{
+ struct flash_info *flash_info = NULL;
+ u8 command[4], response;
+ int i, err;
+ u16 id;
+
+ command[0] = SST25L_CMD_READ_ID;
+ command[1] = 0;
+ command[2] = 0;
+ command[3] = 0;
+ err = spi_write_then_read(spi, command, sizeof(command), &response, 1);
+ if (err < 0) {
+ dev_err(&spi->dev, "error reading device id msb\n");
+ return NULL;
+ }
+
+ id = response << 8;
+
+ command[0] = SST25L_CMD_READ_ID;
+ command[1] = 0;
+ command[2] = 0;
+ command[3] = 1;
+ err = spi_write_then_read(spi, command, sizeof(command), &response, 1);
+ if (err < 0) {
+ dev_err(&spi->dev, "error reading device id lsb\n");
+ return NULL;
+ }
+
+ id |= response;
+
+ for (i = 0; i < ARRAY_SIZE(sst25l_flash_info); i++)
+ if (sst25l_flash_info[i].device_id == id)
+ flash_info = &sst25l_flash_info[i];
+
+ if (!flash_info)
+ dev_err(&spi->dev, "unknown id %.4x\n", id);
+
+ return flash_info;
+}
+
+static int __devinit sst25l_probe(struct spi_device *spi)
+{
+ struct flash_info *flash_info;
+ struct sst25l_flash *flash;
+ struct flash_platform_data *data;
+ int i;
+
+ flash_info = sst25l_match_device(spi);
+ if (!flash_info)
+ return -ENODEV;
+
+ flash = kzalloc(sizeof(struct sst25l_flash), GFP_KERNEL);
+ if (!flash)
+ return -ENOMEM;
+
+ flash->spi = spi;
+ mutex_init(&flash->lock);
+ dev_set_drvdata(&spi->dev, flash);
+
+ data = spi->dev.platform_data;
+ if (data && data->name)
+ flash->mtd.name = data->name;
+ else
+ flash->mtd.name = dev_name(&spi->dev);
+
+ flash->mtd.type = MTD_NORFLASH;
+ flash->mtd.flags = MTD_CAP_NORFLASH;
+ flash->mtd.erasesize = flash_info->erase_size;
+ flash->mtd.writesize = flash_info->page_size;
+ flash->mtd.size = flash_info->page_size * flash_info->nr_pages;
+ flash->mtd.erase = sst25l_erase;
+ flash->mtd.read = sst25l_read;
+ flash->mtd.write = sst25l_write;
+
+ dev_info(&spi->dev, "%s (%lld Kbytes)\n", flash_info->name,
+ (long long)flash->mtd.size >> 10);
+
+ DEBUG(MTD_DEBUG_LEVEL2,
+ "mtd .name = %s, .size = 0x%llx (%lldMiB) "
+ ".erasesize = 0x%.8x (%uKiB) .numeraseregions = %d\n",
+ flash->mtd.name,
+ (long long)flash->mtd.size, (long long)(flash->mtd.size >> 20),
+ flash->mtd.erasesize, flash->mtd.erasesize / 1024,
+ flash->mtd.numeraseregions);
+
+ if (flash->mtd.numeraseregions)
+ for (i = 0; i < flash->mtd.numeraseregions; i++)
+ DEBUG(MTD_DEBUG_LEVEL2,
+ "mtd.eraseregions[%d] = { .offset = 0x%llx, "
+ ".erasesize = 0x%.8x (%uKiB), "
+ ".numblocks = %d }\n",
+ i, (long long)flash->mtd.eraseregions[i].offset,
+ flash->mtd.eraseregions[i].erasesize,
+ flash->mtd.eraseregions[i].erasesize / 1024,
+ flash->mtd.eraseregions[i].numblocks);
+
+ if (mtd_has_partitions()) {
+ struct mtd_partition *parts = NULL;
+ int nr_parts = 0;
+
+ if (mtd_has_cmdlinepart()) {
+ static const char *part_probes[] =
+ {"cmdlinepart", NULL};
+
+ nr_parts = parse_mtd_partitions(&flash->mtd,
+ part_probes,
+ &parts, 0);
+ }
+
+ if (nr_parts <= 0 && data && data->parts) {
+ parts = data->parts;
+ nr_parts = data->nr_parts;
+ }
+
+ if (nr_parts > 0) {
+ for (i = 0; i < nr_parts; i++) {
+ DEBUG(MTD_DEBUG_LEVEL2, "partitions[%d] = "
+ "{.name = %s, .offset = 0x%llx, "
+ ".size = 0x%llx (%lldKiB) }\n",
+ i, parts[i].name,
+ (long long)parts[i].offset,
+ (long long)parts[i].size,
+ (long long)(parts[i].size >> 10));
+ }
+
+ flash->partitioned = 1;
+ return add_mtd_partitions(&flash->mtd,
+ parts, nr_parts);
+ }
+
+ } else if (data->nr_parts) {
+ dev_warn(&spi->dev, "ignoring %d default partitions on %s\n",
+ data->nr_parts, data->name);
+ }
+
+ return add_mtd_device(&flash->mtd) == 1 ? -ENODEV : 0;
+}
+
+static int __devexit sst25l_remove(struct spi_device *spi)
+{
+ struct sst25l_flash *flash = dev_get_drvdata(&spi->dev);
+ int ret;
+
+ if (mtd_has_partitions() && flash->partitioned)
+ ret = del_mtd_partitions(&flash->mtd);
+ else
+ ret = del_mtd_device(&flash->mtd);
+ if (ret == 0)
+ kfree(flash);
+ return ret;
+}
+
+static struct spi_driver sst25l_driver = {
+ .driver = {
+ .name = "sst25l",
+ .bus = &spi_bus_type,
+ .owner = THIS_MODULE,
+ },
+ .probe = sst25l_probe,
+ .remove = __devexit_p(sst25l_remove),
+};
+
+static int __init sst25l_init(void)
+{
+ return spi_register_driver(&sst25l_driver);
+}
+
+static void __exit sst25l_exit(void)
+{
+ spi_unregister_driver(&sst25l_driver);
+}
+
+module_init(sst25l_init);
+module_exit(sst25l_exit);
+
+MODULE_DESCRIPTION("MTD SPI driver for SST25L Flash chips");
+MODULE_AUTHOR("Andre Renaud <andre@bluewatersys.com>, "
+ "Ryan Mallon <ryan@bluewatersys.com>");
+MODULE_LICENSE("GPL");
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH] SST25L (non JEDEC) SPI Flash driver
2009-06-22 3:58 Ryan Mallon
@ 2009-06-22 8:15 ` Artem Bityutskiy
2009-06-22 8:22 ` Linus Walleij
1 sibling, 0 replies; 14+ messages in thread
From: Artem Bityutskiy @ 2009-06-22 8:15 UTC (permalink / raw)
To: Ryan Mallon
Cc: David Woodhouse, linux-mtd, spi-devel-general, mike, linux kernel
On Mon, 2009-06-22 at 15:58 +1200, Ryan Mallon wrote:
> Add support for the non JEDEC SST25L SPI Flash devices.
>
> Signed-off-by: Andre Renaud <andre@bluewatersys.com>
> Signed-off-by: Ryan Mallon <ryan@bluewatersys.com>
> ---
>
> diff --git a/drivers/mtd/devices/Kconfig b/drivers/mtd/devices/Kconfig
> index 325fab9..c222514 100644
> --- a/drivers/mtd/devices/Kconfig
> +++ b/drivers/mtd/devices/Kconfig
> @@ -104,6 +104,16 @@ config M25PXX_USE_FAST_READ
> help
> This option enables FAST_READ access supported by ST M25Pxx.
Would it please be possible to get rid of this warning:
CC [M] drivers/mtd/devices/sst25l.o
drivers/mtd/devices/sst25l.c: In function ‘sst25l_write’:
drivers/mtd/devices/sst25l.c:306: warning: comparison of distinct pointer types lacks a cast
I see it on my x86_64.
--
Best regards,
Artem Bityutskiy (Битюцкий Артём)
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH] SST25L (non JEDEC) SPI Flash driver
2009-06-22 3:58 Ryan Mallon
2009-06-22 8:15 ` Artem Bityutskiy
@ 2009-06-22 8:22 ` Linus Walleij
2009-06-22 21:56 ` Ryan Mallon
1 sibling, 1 reply; 14+ messages in thread
From: Linus Walleij @ 2009-06-22 8:22 UTC (permalink / raw)
To: Ryan Mallon
Cc: David Woodhouse, linux-mtd, spi-devel-general, mike, linux kernel
2009/6/22 Ryan Mallon <ryan@bluewatersys.com>:
> Add support for the non JEDEC SST25L SPI Flash devices.
>
> Signed-off-by: Andre Renaud <andre@bluewatersys.com>
> Signed-off-by: Ryan Mallon <ryan@bluewatersys.com>
> ---
>
> diff --git a/drivers/mtd/devices/Kconfig b/drivers/mtd/devices/Kconfig
> index 325fab9..c222514 100644
> --- a/drivers/mtd/devices/Kconfig
> +++ b/drivers/mtd/devices/Kconfig
> @@ -104,6 +104,16 @@ config M25PXX_USE_FAST_READ
> help
> This option enables FAST_READ access supported by ST M25Pxx.
>
> +config MTD_SST25L
> + tristate "Support SST25L (non JEDEC) SPI Flash chips"
> + depends on SPI_MASTER
> + help
> + This enables access to the non JEDEC SST25L SPI flash chips, used
> + for program and data storage.
> +
> + Set up your spi devices with the right board-specific platform data,
> + if you want to specify device partitioning.
> +
> config MTD_SLRAM
> tristate "Uncached system RAM"
> help
> diff --git a/drivers/mtd/devices/Makefile b/drivers/mtd/devices/Makefile
> index 0993d5c..ab5c9b9 100644
> --- a/drivers/mtd/devices/Makefile
> +++ b/drivers/mtd/devices/Makefile
> @@ -16,3 +16,4 @@ obj-$(CONFIG_MTD_LART) += lart.o
> obj-$(CONFIG_MTD_BLOCK2MTD) += block2mtd.o
> obj-$(CONFIG_MTD_DATAFLASH) += mtd_dataflash.o
> obj-$(CONFIG_MTD_M25P80) += m25p80.o
> +obj-$(CONFIG_MTD_SST25L) += sst25l.o
> diff --git a/drivers/mtd/devices/sst25l.c b/drivers/mtd/devices/sst25l.c
> new file mode 100644
> index 0000000..8e62479
> --- /dev/null
> +++ b/drivers/mtd/devices/sst25l.c
> @@ -0,0 +1,508 @@
> +/*
> + * drivers/mtd/sst25l.c
> + *
> + * Driver for SST25L SPI Flash chips
> + *
> + * Copyright (C) 2009 Bluewater Systems Ltd
> + * Author: Andre Renaud <andre@bluewatersys.com>
> + * Author: Ryan Mallon <ryan@bluewatersys.com>
> + *
> + * Based on m25p80.c
> + *
> + * This code is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + */
> +
> +#include <linux/init.h>
> +#include <linux/module.h>
> +#include <linux/device.h>
> +#include <linux/mutex.h>
> +#include <linux/math64.h>
I'm always suspicious about drivers doing maths, so I
think this can be removed, see below...
> +#include <linux/interrupt.h>
> +
> +#include <linux/mtd/mtd.h>
> +#include <linux/mtd/partitions.h>
> +
> +#include <linux/spi/spi.h>
> +#include <linux/spi/flash.h>
> +
> +/* Erases can take up to 3 seconds! */
> +#define MAX_READY_WAIT_JIFFIES msecs_to_jiffies(3000)
> +
> +#define SST25L_CMD_WRSR 0x01 /* Write status register */
> +#define SST25L_CMD_WRDI 0x04 /* Write disable */
> +#define SST25L_CMD_RDSR 0x05 /* Read status register */
> +#define SST25L_CMD_WREN 0x06 /* Write enable */
> +#define SST25L_CMD_READ 0x03 /* High speed read */
> +
> +#define SST25L_CMD_EWSR 0x50 /* Enable write status register */
> +#define SST25L_CMD_BLOCK_ERASE 0x52 /* Erase sector */
> +#define SST25L_CMD_READ_ID 0x90 /* Read device ID */
> +#define SST25L_CMD_AAI_PROGRAM 0xaf /* Auto address increment */
> +
> +#define SST25L_STATUS_BUSY (1 << 0) /* Chip is busy */
> +#define SST25L_STATUS_WREN (1 << 1) /* Write enabled */
> +#define SST25L_STATUS_BP0 (1 << 2) /* Block protection 0 */
> +#define SST25L_STATUS_BP1 (1 << 3) /* Block protection 1 */
> +
> +struct sst25l_flash {
> + struct spi_device *spi;
> + struct mutex lock;
> + struct mtd_info mtd;
> +
> + int partitioned;
> +};
> +
> +struct flash_info {
> + const char *name;
> + u16 device_id;
> + unsigned page_size;
> + unsigned nr_pages;
> + unsigned erase_size;
> +};
> +
> +#define to_sst25l_flash(x) container_of(x, struct sst25l_flash, mtd)
> +
> +static struct flash_info __devinitdata sst25l_flash_info[] = {
__devinitdata or __initdata?
Check the definitions from include/linux/init.h. If it's hot-pluggable
then it's fine!
> + {"sst25lf020a", 0xbf43, 256, 1024, 32 * 1024},
> + {"sst25lf040a", 0xbf44, 256, 2048, 32 * 1024},
> +};
> +
> +static int sst25l_status(struct sst25l_flash *flash, int *status)
> +{
> + u8 command, response;
> + int err;
> +
> + command = SST25L_CMD_RDSR;
> + err = spi_write_then_read(flash->spi, &command, 1, &response, 1);
> + if (err < 0)
> + return err;
> +
> + *status = response;
> + return 0;
> +}
> +
> +static int sst25l_write_enable(struct sst25l_flash *flash, int enable)
> +{
> + u8 command[2];
> + int status, err;
> +
> + command[0] = enable ? SST25L_CMD_WREN : SST25L_CMD_WRDI;
> + err = spi_write(flash->spi, command, 1);
> + if (err)
> + return err;
> +
> + command[0] = SST25L_CMD_EWSR;
> + err = spi_write(flash->spi, command, 1);
> + if (err)
> + return err;
> +
> + command[0] = SST25L_CMD_WRSR;
> + command[1] = enable ? 0 : SST25L_STATUS_BP0 | SST25L_STATUS_BP1;
> + err = spi_write(flash->spi, command, 2);
> + if (err)
> + return err;
> +
> + if (enable) {
> + err = sst25l_status(flash, &status);
> + if (err)
> + return err;
> + if (!(status & SST25L_STATUS_WREN))
> + return -EROFS;
> + }
> +
> + return 0;
> +}
> +
> +static int sst25l_wait_till_ready(struct sst25l_flash *flash)
> +{
> + unsigned long deadline;
> + int status, err;
> +
> + deadline = jiffies + MAX_READY_WAIT_JIFFIES;
> + do {
> + err = sst25l_status(flash, &status);
> + if (err)
> + return err;
> + if (!(status & SST25L_STATUS_BUSY))
> + return 0;
> +
> + cond_resched();
> + } while (!time_after_eq(jiffies, deadline));
> +
> + return -ETIMEDOUT;
> +}
> +
> +static int sst25l_erase_block(struct sst25l_flash *flash, u32 offset)
> +{
> + u8 command[4];
> + int err;
> +
> + err = sst25l_write_enable(flash, 1);
> + if (err)
> + return err;
> +
> + command[0] = SST25L_CMD_BLOCK_ERASE;
> + command[1] = offset >> 16;
> + command[2] = offset >> 8;
> + command[3] = offset;
> + err = spi_write(flash->spi, command, 4);
> + if (err)
> + return err;
> +
> + err = sst25l_wait_till_ready(flash);
> + if (err)
> + return err;
> +
> + return sst25l_write_enable(flash, 0);
> +}
> +
> +static int sst25l_erase(struct mtd_info *mtd, struct erase_info *instr)
> +{
> + struct sst25l_flash *flash = to_sst25l_flash(mtd);
> + u32 addr, end, rem;
> + int err;
> +
> + /* Sanity checks */
> + if (instr->addr + instr->len > flash->mtd.size)
> + return -EINVAL;
> +
> + div_u64_rem(instr->len, mtd->erasesize, &rem);
if ((instr->len & 0xFFFFFFFFU) % mtd->erasesize)
return -EINVAL;
> + if (rem)
> + return -EINVAL;
> +
> + div_u64_rem(instr->addr, mtd->erasesize, &rem);
Same thing again.
> + if (rem)
> + return -EINVAL;
> +
> + addr = instr->addr;
> + end = addr + instr->len;
> +
> + mutex_lock(&flash->lock);
> +
> + err = sst25l_wait_till_ready(flash);
> + if (err)
> + return err;
> +
> + while (addr < end) {
> + err = sst25l_erase_block(flash, addr);
> + if (err) {
> + mutex_unlock(&flash->lock);
> + instr->state = MTD_ERASE_FAILED;
> + dev_err(&flash->spi->dev, "Erase failed\n");
> + return err;
> + }
> +
> + addr += mtd->erasesize;
> + }
> +
> + mutex_unlock(&flash->lock);
> +
> + instr->state = MTD_ERASE_DONE;
> + mtd_erase_callback(instr);
> + return 0;
> +}
> +
> +static int sst25l_read(struct mtd_info *mtd, loff_t from, size_t len,
> + size_t *retlen, u_char *buf)
> +{
> + struct sst25l_flash *flash = to_sst25l_flash(mtd);
> + struct spi_transfer transfer[2];
> + struct spi_message message;
> + u8 command[4];
> + int ret;
> +
> + /* Sanity checking */
> + if (len == 0)
> + return 0;
> +
> + if (from + len > flash->mtd.size)
> + return -EINVAL;
> +
> + if (retlen)
> + *retlen = 0;
> +
> + spi_message_init(&message);
> + memset(&transfer, 0, sizeof(transfer));
> +
> + command[0] = SST25L_CMD_READ;
> + command[1] = from >> 16;
> + command[2] = from >> 8;
> + command[3] = from;
> +
> + transfer[0].tx_buf = command;
> + transfer[0].len = sizeof(command);
> + spi_message_add_tail(&transfer[0], &message);
> +
> + transfer[1].rx_buf = buf;
> + transfer[1].len = len;
> + spi_message_add_tail(&transfer[1], &message);
> +
> + mutex_lock(&flash->lock);
> +
> + /* Wait for previous write/erase to complete */
> + ret = sst25l_wait_till_ready(flash);
> + if (ret) {
> + mutex_unlock(&flash->lock);
> + return ret;
> + }
> +
> + spi_sync(flash->spi, &message);
> +
> + if (retlen && message.actual_length > sizeof(command))
> + *retlen += message.actual_length - sizeof(command);
> +
> + mutex_unlock(&flash->lock);
> + return 0;
> +}
> +
> +static int sst25l_write(struct mtd_info *mtd, loff_t to, size_t len,
> + size_t *retlen, const u_char *buf)
> +{
> + struct sst25l_flash *flash = to_sst25l_flash(mtd);
> + int i, j, ret, bytes, copied = 0;
> + u8 command[5];
> + u32 rem;
> +
> + /* Sanity checks */
> + if (!len)
> + return 0;
> +
> + if (to + len > flash->mtd.size)
> + return -EINVAL;
> +
> + div_u64_rem(to, mtd->writesize, &rem);
I see. But do you really want to do that with maths?
Can't you just do some simple arithmetics since mtd->writesize is
u32 anything in the higher 32 bits can be ignored (it will be
divisible with any u32 value anyway!), so
/* Need to be evenly divisible by writesize */
if ((to & 0xFFFFFFFFU) % mtd->writesize)
return -EINVAL;
does this work?
> + if (rem)
> + return -EINVAL;
> +
> + mutex_lock(&flash->lock);
> +
> + ret = sst25l_write_enable(flash, 1);
> + if (ret)
> + goto out;
> +
> + for (i = 0; i < len; i += mtd->writesize) {
> + ret = sst25l_wait_till_ready(flash);
> + if (ret)
> + goto out;
> +
> + /* Write the first byte of the page */
> + command[0] = SST25L_CMD_AAI_PROGRAM;
> + command[1] = (to + i) >> 16;
> + command[2] = (to + i) >> 8;
> + command[3] = (to + i);
> + command[4] = buf[i];
> + ret = spi_write(flash->spi, command, 5);
> + if (ret < 0)
> + goto out;
> + copied++;
> +
> + /*
> + * Write the remaining bytes using auto address
> + * increment mode
> + */
> + bytes = min(mtd->writesize, len - i);
> + for (j = 1; j < bytes; j++, copied++) {
> + ret = sst25l_wait_till_ready(flash);
> + if (ret)
> + goto out;
> +
> + command[1] = buf[i + j];
> + ret = spi_write(flash->spi, command, 2);
> + if (ret)
> + goto out;
> + }
> + }
> +
> +out:
> + ret = sst25l_write_enable(flash, 0);
> +
> + if (retlen)
> + *retlen = copied;
> +
> + mutex_unlock(&flash->lock);
> + return ret;
> +}
> +
> +static struct flash_info *__devinit sst25l_match_device(struct spi_device *spi)
Or is it __init really? NB: this will save (very little) memory on your system.
> +{
> + struct flash_info *flash_info = NULL;
> + u8 command[4], response;
> + int i, err;
> + u16 id;
> +
> + command[0] = SST25L_CMD_READ_ID;
> + command[1] = 0;
> + command[2] = 0;
> + command[3] = 0;
> + err = spi_write_then_read(spi, command, sizeof(command), &response, 1);
> + if (err < 0) {
> + dev_err(&spi->dev, "error reading device id msb\n");
> + return NULL;
> + }
> +
> + id = response << 8;
> +
> + command[0] = SST25L_CMD_READ_ID;
> + command[1] = 0;
> + command[2] = 0;
> + command[3] = 1;
> + err = spi_write_then_read(spi, command, sizeof(command), &response, 1);
> + if (err < 0) {
> + dev_err(&spi->dev, "error reading device id lsb\n");
> + return NULL;
> + }
> +
> + id |= response;
> +
> + for (i = 0; i < ARRAY_SIZE(sst25l_flash_info); i++)
> + if (sst25l_flash_info[i].device_id == id)
> + flash_info = &sst25l_flash_info[i];
> +
> + if (!flash_info)
> + dev_err(&spi->dev, "unknown id %.4x\n", id);
> +
> + return flash_info;
> +}
> +
> +static int __devinit sst25l_probe(struct spi_device *spi)
Or __init?
> +{
> + struct flash_info *flash_info;
> + struct sst25l_flash *flash;
> + struct flash_platform_data *data;
> + int i;
> +
> + flash_info = sst25l_match_device(spi);
> + if (!flash_info)
> + return -ENODEV;
> +
> + flash = kzalloc(sizeof(struct sst25l_flash), GFP_KERNEL);
> + if (!flash)
> + return -ENOMEM;
> +
> + flash->spi = spi;
> + mutex_init(&flash->lock);
> + dev_set_drvdata(&spi->dev, flash);
> +
> + data = spi->dev.platform_data;
> + if (data && data->name)
> + flash->mtd.name = data->name;
> + else
> + flash->mtd.name = dev_name(&spi->dev);
> +
> + flash->mtd.type = MTD_NORFLASH;
> + flash->mtd.flags = MTD_CAP_NORFLASH;
> + flash->mtd.erasesize = flash_info->erase_size;
> + flash->mtd.writesize = flash_info->page_size;
> + flash->mtd.size = flash_info->page_size * flash_info->nr_pages;
> + flash->mtd.erase = sst25l_erase;
> + flash->mtd.read = sst25l_read;
> + flash->mtd.write = sst25l_write;
> +
> + dev_info(&spi->dev, "%s (%lld Kbytes)\n", flash_info->name,
> + (long long)flash->mtd.size >> 10);
> +
> + DEBUG(MTD_DEBUG_LEVEL2,
> + "mtd .name = %s, .size = 0x%llx (%lldMiB) "
> + ".erasesize = 0x%.8x (%uKiB) .numeraseregions = %d\n",
> + flash->mtd.name,
> + (long long)flash->mtd.size, (long long)(flash->mtd.size >> 20),
> + flash->mtd.erasesize, flash->mtd.erasesize / 1024,
> + flash->mtd.numeraseregions);
> +
> + if (flash->mtd.numeraseregions)
> + for (i = 0; i < flash->mtd.numeraseregions; i++)
> + DEBUG(MTD_DEBUG_LEVEL2,
> + "mtd.eraseregions[%d] = { .offset = 0x%llx, "
> + ".erasesize = 0x%.8x (%uKiB), "
> + ".numblocks = %d }\n",
> + i, (long long)flash->mtd.eraseregions[i].offset,
> + flash->mtd.eraseregions[i].erasesize,
> + flash->mtd.eraseregions[i].erasesize / 1024,
> + flash->mtd.eraseregions[i].numblocks);
> +
> + if (mtd_has_partitions()) {
> + struct mtd_partition *parts = NULL;
> + int nr_parts = 0;
> +
> + if (mtd_has_cmdlinepart()) {
> + static const char *part_probes[] =
> + {"cmdlinepart", NULL};
> +
> + nr_parts = parse_mtd_partitions(&flash->mtd,
> + part_probes,
> + &parts, 0);
> + }
> +
> + if (nr_parts <= 0 && data && data->parts) {
> + parts = data->parts;
> + nr_parts = data->nr_parts;
> + }
> +
> + if (nr_parts > 0) {
> + for (i = 0; i < nr_parts; i++) {
> + DEBUG(MTD_DEBUG_LEVEL2, "partitions[%d] = "
> + "{.name = %s, .offset = 0x%llx, "
> + ".size = 0x%llx (%lldKiB) }\n",
> + i, parts[i].name,
> + (long long)parts[i].offset,
> + (long long)parts[i].size,
> + (long long)(parts[i].size >> 10));
> + }
> +
> + flash->partitioned = 1;
> + return add_mtd_partitions(&flash->mtd,
> + parts, nr_parts);
> + }
> +
> + } else if (data->nr_parts) {
> + dev_warn(&spi->dev, "ignoring %d default partitions on %s\n",
> + data->nr_parts, data->name);
> + }
> +
> + return add_mtd_device(&flash->mtd) == 1 ? -ENODEV : 0;
> +}
> +
> +static int __devexit sst25l_remove(struct spi_device *spi)
Or __exit?
> +{
> + struct sst25l_flash *flash = dev_get_drvdata(&spi->dev);
> + int ret;
> +
> + if (mtd_has_partitions() && flash->partitioned)
> + ret = del_mtd_partitions(&flash->mtd);
> + else
> + ret = del_mtd_device(&flash->mtd);
> + if (ret == 0)
> + kfree(flash);
> + return ret;
> +}
> +
> +static struct spi_driver sst25l_driver = {
> + .driver = {
> + .name = "sst25l",
> + .bus = &spi_bus_type,
> + .owner = THIS_MODULE,
> + },
> + .probe = sst25l_probe,
> + .remove = __devexit_p(sst25l_remove),
Or __exit_p()?
> +};
> +
> +static int __init sst25l_init(void)
> +{
> + return spi_register_driver(&sst25l_driver);
> +}
> +
> +static void __exit sst25l_exit(void)
> +{
> + spi_unregister_driver(&sst25l_driver);
> +}
> +
> +module_init(sst25l_init);
> +module_exit(sst25l_exit);
> +
> +MODULE_DESCRIPTION("MTD SPI driver for SST25L Flash chips");
> +MODULE_AUTHOR("Andre Renaud <andre@bluewatersys.com>, "
> + "Ryan Mallon <ryan@bluewatersys.com>");
> +MODULE_LICENSE("GPL");
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at http://www.tux.org/lkml/
>
Yours,
Linus Walleij
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH] SST25L (non JEDEC) SPI Flash driver
2009-06-22 8:22 ` Linus Walleij
@ 2009-06-22 21:56 ` Ryan Mallon
2009-06-23 7:04 ` Linus Walleij
0 siblings, 1 reply; 14+ messages in thread
From: Ryan Mallon @ 2009-06-22 21:56 UTC (permalink / raw)
To: Linus Walleij
Cc: David Woodhouse, linux-mtd, spi-devel-general, mike, linux kernel,
dedekind
Hi Linus,
Thanks for the comments. I have removed the dependency on math64.h, I
changed the instances to u32 casts and mod operator (the sst25l flash
chips are quite small so it won't be a problem).
I have also replaced the __devinit/__devexit markers with the
__init/__exit equivalents. In our particular instance the chip is
soldered onto the board so its not hot-pluggable. I think this will be
the case for most people who are using these chips.
Out of curiosity: I'm not too clear on what makes a particular chip
hot-pluggable. I think technically the sst25l chip could be put onto a
hot-pluggable board or power domain. Can't most devices be made
hot-pluggable? Is the general rule to make devices non hot-pluggable if
most/all boards in the mainline do not allow it to be hot-plugged?
Also fixed a warning on the arguments to the min macro pointed out by
Artem Bityitskiy.
Updated patch below:
---
Add support for SST25L (non JEDEC) SPI Flash chips
Signed-off-by: Andre Renaud <andre@bluewatersys.com>
Signed-off-by: Ryan Mallon <ryan@bluewatersys.com>
---
diff --git a/drivers/mtd/devices/Kconfig b/drivers/mtd/devices/Kconfig
index 325fab9..c222514 100644
--- a/drivers/mtd/devices/Kconfig
+++ b/drivers/mtd/devices/Kconfig
@@ -104,6 +104,16 @@ config M25PXX_USE_FAST_READ
help
This option enables FAST_READ access supported by ST M25Pxx.
+config MTD_SST25L
+ tristate "Support SST25L (non JEDEC) SPI Flash chips"
+ depends on SPI_MASTER
+ help
+ This enables access to the non JEDEC SST25L SPI flash chips, used
+ for program and data storage.
+
+ Set up your spi devices with the right board-specific platform data,
+ if you want to specify device partitioning.
+
config MTD_SLRAM
tristate "Uncached system RAM"
help
diff --git a/drivers/mtd/devices/Makefile b/drivers/mtd/devices/Makefile
index 0993d5c..ab5c9b9 100644
--- a/drivers/mtd/devices/Makefile
+++ b/drivers/mtd/devices/Makefile
@@ -16,3 +16,4 @@ obj-$(CONFIG_MTD_LART) += lart.o
obj-$(CONFIG_MTD_BLOCK2MTD) += block2mtd.o
obj-$(CONFIG_MTD_DATAFLASH) += mtd_dataflash.o
obj-$(CONFIG_MTD_M25P80) += m25p80.o
+obj-$(CONFIG_MTD_SST25L) += sst25l.o
diff --git a/drivers/mtd/devices/sst25l.c b/drivers/mtd/devices/sst25l.c
new file mode 100644
index 0000000..30658f6
--- /dev/null
+++ b/drivers/mtd/devices/sst25l.c
@@ -0,0 +1,503 @@
+/*
+ * drivers/mtd/sst25l.c
+ *
+ * Driver for SST25L SPI Flash chips
+ *
+ * Copyright (C) 2009 Bluewater Systems Ltd
+ * Author: Andre Renaud <andre@bluewatersys.com>
+ * Author: Ryan Mallon <ryan@bluewatersys.com>
+ *
+ * Based on m25p80.c
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/mutex.h>
+#include <linux/interrupt.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+
+#include <linux/spi/spi.h>
+#include <linux/spi/flash.h>
+
+/* Erases can take up to 3 seconds! */
+#define MAX_READY_WAIT_JIFFIES msecs_to_jiffies(3000)
+
+#define SST25L_CMD_WRSR 0x01 /* Write status register */
+#define SST25L_CMD_WRDI 0x04 /* Write disable */
+#define SST25L_CMD_RDSR 0x05 /* Read status register */
+#define SST25L_CMD_WREN 0x06 /* Write enable */
+#define SST25L_CMD_READ 0x03 /* High speed read */
+
+#define SST25L_CMD_EWSR 0x50 /* Enable write status register */
+#define SST25L_CMD_BLOCK_ERASE 0x52 /* Erase sector */
+#define SST25L_CMD_READ_ID 0x90 /* Read device ID */
+#define SST25L_CMD_AAI_PROGRAM 0xaf /* Auto address increment */
+
+#define SST25L_STATUS_BUSY (1 << 0) /* Chip is busy */
+#define SST25L_STATUS_WREN (1 << 1) /* Write enabled */
+#define SST25L_STATUS_BP0 (1 << 2) /* Block protection 0 */
+#define SST25L_STATUS_BP1 (1 << 3) /* Block protection 1 */
+
+struct sst25l_flash {
+ struct spi_device *spi;
+ struct mutex lock;
+ struct mtd_info mtd;
+
+ int partitioned;
+};
+
+struct flash_info {
+ const char *name;
+ u16 device_id;
+ unsigned page_size;
+ unsigned nr_pages;
+ unsigned erase_size;
+};
+
+#define to_sst25l_flash(x) container_of(x, struct sst25l_flash, mtd)
+
+static struct flash_info __initdata sst25l_flash_info[] = {
+ {"sst25lf020a", 0xbf43, 256, 1024, 32 * 1024},
+ {"sst25lf040a", 0xbf44, 256, 2048, 32 * 1024},
+};
+
+static int sst25l_status(struct sst25l_flash *flash, int *status)
+{
+ u8 command, response;
+ int err;
+
+ command = SST25L_CMD_RDSR;
+ err = spi_write_then_read(flash->spi, &command, 1, &response, 1);
+ if (err < 0)
+ return err;
+
+ *status = response;
+ return 0;
+}
+
+static int sst25l_write_enable(struct sst25l_flash *flash, int enable)
+{
+ u8 command[2];
+ int status, err;
+
+ command[0] = enable ? SST25L_CMD_WREN : SST25L_CMD_WRDI;
+ err = spi_write(flash->spi, command, 1);
+ if (err)
+ return err;
+
+ command[0] = SST25L_CMD_EWSR;
+ err = spi_write(flash->spi, command, 1);
+ if (err)
+ return err;
+
+ command[0] = SST25L_CMD_WRSR;
+ command[1] = enable ? 0 : SST25L_STATUS_BP0 | SST25L_STATUS_BP1;
+ err = spi_write(flash->spi, command, 2);
+ if (err)
+ return err;
+
+ if (enable) {
+ err = sst25l_status(flash, &status);
+ if (err)
+ return err;
+ if (!(status & SST25L_STATUS_WREN))
+ return -EROFS;
+ }
+
+ return 0;
+}
+
+static int sst25l_wait_till_ready(struct sst25l_flash *flash)
+{
+ unsigned long deadline;
+ int status, err;
+
+ deadline = jiffies + MAX_READY_WAIT_JIFFIES;
+ do {
+ err = sst25l_status(flash, &status);
+ if (err)
+ return err;
+ if (!(status & SST25L_STATUS_BUSY))
+ return 0;
+
+ cond_resched();
+ } while (!time_after_eq(jiffies, deadline));
+
+ return -ETIMEDOUT;
+}
+
+static int sst25l_erase_block(struct sst25l_flash *flash, u32 offset)
+{
+ u8 command[4];
+ int err;
+
+ err = sst25l_write_enable(flash, 1);
+ if (err)
+ return err;
+
+ command[0] = SST25L_CMD_BLOCK_ERASE;
+ command[1] = offset >> 16;
+ command[2] = offset >> 8;
+ command[3] = offset;
+ err = spi_write(flash->spi, command, 4);
+ if (err)
+ return err;
+
+ err = sst25l_wait_till_ready(flash);
+ if (err)
+ return err;
+
+ return sst25l_write_enable(flash, 0);
+}
+
+static int sst25l_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+ struct sst25l_flash *flash = to_sst25l_flash(mtd);
+ u32 addr, end;
+ int err;
+
+ /* Sanity checks */
+ if (instr->addr + instr->len > flash->mtd.size)
+ return -EINVAL;
+
+ if ((u32)instr->len % mtd->erasesize)
+ return -EINVAL;
+
+ if ((u32)instr->addr % mtd->erasesize)
+ return -EINVAL;
+
+ addr = instr->addr;
+ end = addr + instr->len;
+
+ mutex_lock(&flash->lock);
+
+ err = sst25l_wait_till_ready(flash);
+ if (err)
+ return err;
+
+ while (addr < end) {
+ err = sst25l_erase_block(flash, addr);
+ if (err) {
+ mutex_unlock(&flash->lock);
+ instr->state = MTD_ERASE_FAILED;
+ dev_err(&flash->spi->dev, "Erase failed\n");
+ return err;
+ }
+
+ addr += mtd->erasesize;
+ }
+
+ mutex_unlock(&flash->lock);
+
+ instr->state = MTD_ERASE_DONE;
+ mtd_erase_callback(instr);
+ return 0;
+}
+
+static int sst25l_read(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ struct sst25l_flash *flash = to_sst25l_flash(mtd);
+ struct spi_transfer transfer[2];
+ struct spi_message message;
+ u8 command[4];
+ int ret;
+
+ /* Sanity checking */
+ if (len == 0)
+ return 0;
+
+ if (from + len > flash->mtd.size)
+ return -EINVAL;
+
+ if (retlen)
+ *retlen = 0;
+
+ spi_message_init(&message);
+ memset(&transfer, 0, sizeof(transfer));
+
+ command[0] = SST25L_CMD_READ;
+ command[1] = from >> 16;
+ command[2] = from >> 8;
+ command[3] = from;
+
+ transfer[0].tx_buf = command;
+ transfer[0].len = sizeof(command);
+ spi_message_add_tail(&transfer[0], &message);
+
+ transfer[1].rx_buf = buf;
+ transfer[1].len = len;
+ spi_message_add_tail(&transfer[1], &message);
+
+ mutex_lock(&flash->lock);
+
+ /* Wait for previous write/erase to complete */
+ ret = sst25l_wait_till_ready(flash);
+ if (ret) {
+ mutex_unlock(&flash->lock);
+ return ret;
+ }
+
+ spi_sync(flash->spi, &message);
+
+ if (retlen && message.actual_length > sizeof(command))
+ *retlen += message.actual_length - sizeof(command);
+
+ mutex_unlock(&flash->lock);
+ return 0;
+}
+
+static int sst25l_write(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf)
+{
+ struct sst25l_flash *flash = to_sst25l_flash(mtd);
+ int i, j, ret, bytes, copied = 0;
+ u8 command[5];
+
+ /* Sanity checks */
+ if (!len)
+ return 0;
+
+ if (to + len > flash->mtd.size)
+ return -EINVAL;
+
+ if ((u32)to % mtd->writesize)
+ return -EINVAL;
+
+ mutex_lock(&flash->lock);
+
+ ret = sst25l_write_enable(flash, 1);
+ if (ret)
+ goto out;
+
+ for (i = 0; i < len; i += mtd->writesize) {
+ ret = sst25l_wait_till_ready(flash);
+ if (ret)
+ goto out;
+
+ /* Write the first byte of the page */
+ command[0] = SST25L_CMD_AAI_PROGRAM;
+ command[1] = (to + i) >> 16;
+ command[2] = (to + i) >> 8;
+ command[3] = (to + i);
+ command[4] = buf[i];
+ ret = spi_write(flash->spi, command, 5);
+ if (ret < 0)
+ goto out;
+ copied++;
+
+ /*
+ * Write the remaining bytes using auto address
+ * increment mode
+ */
+ bytes = min(mtd->writesize, (u32)(len - i));
+ for (j = 1; j < bytes; j++, copied++) {
+ ret = sst25l_wait_till_ready(flash);
+ if (ret)
+ goto out;
+
+ command[1] = buf[i + j];
+ ret = spi_write(flash->spi, command, 2);
+ if (ret)
+ goto out;
+ }
+ }
+
+out:
+ ret = sst25l_write_enable(flash, 0);
+
+ if (retlen)
+ *retlen = copied;
+
+ mutex_unlock(&flash->lock);
+ return ret;
+}
+
+static struct flash_info *__init sst25l_match_device(struct spi_device *spi)
+{
+ struct flash_info *flash_info = NULL;
+ u8 command[4], response;
+ int i, err;
+ u16 id;
+
+ command[0] = SST25L_CMD_READ_ID;
+ command[1] = 0;
+ command[2] = 0;
+ command[3] = 0;
+ err = spi_write_then_read(spi, command, sizeof(command), &response, 1);
+ if (err < 0) {
+ dev_err(&spi->dev, "error reading device id msb\n");
+ return NULL;
+ }
+
+ id = response << 8;
+
+ command[0] = SST25L_CMD_READ_ID;
+ command[1] = 0;
+ command[2] = 0;
+ command[3] = 1;
+ err = spi_write_then_read(spi, command, sizeof(command), &response, 1);
+ if (err < 0) {
+ dev_err(&spi->dev, "error reading device id lsb\n");
+ return NULL;
+ }
+
+ id |= response;
+
+ for (i = 0; i < ARRAY_SIZE(sst25l_flash_info); i++)
+ if (sst25l_flash_info[i].device_id == id)
+ flash_info = &sst25l_flash_info[i];
+
+ if (!flash_info)
+ dev_err(&spi->dev, "unknown id %.4x\n", id);
+
+ return flash_info;
+}
+
+static int __init sst25l_probe(struct spi_device *spi)
+{
+ struct flash_info *flash_info;
+ struct sst25l_flash *flash;
+ struct flash_platform_data *data;
+ int i;
+
+ flash_info = sst25l_match_device(spi);
+ if (!flash_info)
+ return -ENODEV;
+
+ flash = kzalloc(sizeof(struct sst25l_flash), GFP_KERNEL);
+ if (!flash)
+ return -ENOMEM;
+
+ flash->spi = spi;
+ mutex_init(&flash->lock);
+ dev_set_drvdata(&spi->dev, flash);
+
+ data = spi->dev.platform_data;
+ if (data && data->name)
+ flash->mtd.name = data->name;
+ else
+ flash->mtd.name = dev_name(&spi->dev);
+
+ flash->mtd.type = MTD_NORFLASH;
+ flash->mtd.flags = MTD_CAP_NORFLASH;
+ flash->mtd.erasesize = flash_info->erase_size;
+ flash->mtd.writesize = flash_info->page_size;
+ flash->mtd.size = flash_info->page_size * flash_info->nr_pages;
+ flash->mtd.erase = sst25l_erase;
+ flash->mtd.read = sst25l_read;
+ flash->mtd.write = sst25l_write;
+
+ dev_info(&spi->dev, "%s (%lld Kbytes)\n", flash_info->name,
+ (long long)flash->mtd.size >> 10);
+
+ DEBUG(MTD_DEBUG_LEVEL2,
+ "mtd .name = %s, .size = 0x%llx (%lldMiB) "
+ ".erasesize = 0x%.8x (%uKiB) .numeraseregions = %d\n",
+ flash->mtd.name,
+ (long long)flash->mtd.size, (long long)(flash->mtd.size >> 20),
+ flash->mtd.erasesize, flash->mtd.erasesize / 1024,
+ flash->mtd.numeraseregions);
+
+ if (flash->mtd.numeraseregions)
+ for (i = 0; i < flash->mtd.numeraseregions; i++)
+ DEBUG(MTD_DEBUG_LEVEL2,
+ "mtd.eraseregions[%d] = { .offset = 0x%llx, "
+ ".erasesize = 0x%.8x (%uKiB), "
+ ".numblocks = %d }\n",
+ i, (long long)flash->mtd.eraseregions[i].offset,
+ flash->mtd.eraseregions[i].erasesize,
+ flash->mtd.eraseregions[i].erasesize / 1024,
+ flash->mtd.eraseregions[i].numblocks);
+
+ if (mtd_has_partitions()) {
+ struct mtd_partition *parts = NULL;
+ int nr_parts = 0;
+
+ if (mtd_has_cmdlinepart()) {
+ static const char *part_probes[] =
+ {"cmdlinepart", NULL};
+
+ nr_parts = parse_mtd_partitions(&flash->mtd,
+ part_probes,
+ &parts, 0);
+ }
+
+ if (nr_parts <= 0 && data && data->parts) {
+ parts = data->parts;
+ nr_parts = data->nr_parts;
+ }
+
+ if (nr_parts > 0) {
+ for (i = 0; i < nr_parts; i++) {
+ DEBUG(MTD_DEBUG_LEVEL2, "partitions[%d] = "
+ "{.name = %s, .offset = 0x%llx, "
+ ".size = 0x%llx (%lldKiB) }\n",
+ i, parts[i].name,
+ (long long)parts[i].offset,
+ (long long)parts[i].size,
+ (long long)(parts[i].size >> 10));
+ }
+
+ flash->partitioned = 1;
+ return add_mtd_partitions(&flash->mtd,
+ parts, nr_parts);
+ }
+
+ } else if (data->nr_parts) {
+ dev_warn(&spi->dev, "ignoring %d default partitions on %s\n",
+ data->nr_parts, data->name);
+ }
+
+ return add_mtd_device(&flash->mtd) == 1 ? -ENODEV : 0;
+}
+
+static int __exit sst25l_remove(struct spi_device *spi)
+{
+ struct sst25l_flash *flash = dev_get_drvdata(&spi->dev);
+ int ret;
+
+ if (mtd_has_partitions() && flash->partitioned)
+ ret = del_mtd_partitions(&flash->mtd);
+ else
+ ret = del_mtd_device(&flash->mtd);
+ if (ret == 0)
+ kfree(flash);
+ return ret;
+}
+
+static struct spi_driver sst25l_driver = {
+ .driver = {
+ .name = "sst25l",
+ .bus = &spi_bus_type,
+ .owner = THIS_MODULE,
+ },
+ .probe = sst25l_probe,
+ .remove = __exit_p(sst25l_remove),
+};
+
+static int __init sst25l_init(void)
+{
+ return spi_register_driver(&sst25l_driver);
+}
+
+static void __exit sst25l_exit(void)
+{
+ spi_unregister_driver(&sst25l_driver);
+}
+
+module_init(sst25l_init);
+module_exit(sst25l_exit);
+
+MODULE_DESCRIPTION("MTD SPI driver for SST25L Flash chips");
+MODULE_AUTHOR("Andre Renaud <andre@bluewatersys.com>, "
+ "Ryan Mallon <ryan@bluewatersys.com>");
+MODULE_LICENSE("GPL");
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH] SST25L (non JEDEC) SPI Flash driver
2009-06-22 21:56 ` Ryan Mallon
@ 2009-06-23 7:04 ` Linus Walleij
2009-06-23 20:48 ` Ryan Mallon
0 siblings, 1 reply; 14+ messages in thread
From: Linus Walleij @ 2009-06-23 7:04 UTC (permalink / raw)
To: Ryan Mallon
Cc: David Woodhouse, linux-mtd, spi-devel-general, mike, linux kernel,
dedekind
2009/6/22 Ryan Mallon <ryan@bluewatersys.com>:
> Out of curiosity: I'm not too clear on what makes a particular chip
> hot-pluggable. I think technically the sst25l chip could be put onto a
> hot-pluggable board or power domain. Can't most devices be made
> hot-pluggable? Is the general rule to make devices non hot-pluggable if
> most/all boards in the mainline do not allow it to be hot-plugged?
I haven't seen any rule about it, but nominally the drivers support the
configurations found in the kernel tree, so if some board physically
existing and soon-to-run linux hotplugs chips like this, then have it
__dev{init|exit} else __{init|exit} IMHO.
Of course you can design for all plausible use cases but it will pile up
indefinitely. Personally I try to not design software for a hardware until it
exists, and I like the IETF catch-phrase "rough consensus and running
code" and this sort of fits that :-)
Yours,
Linus Walleij
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH] SST25L (non JEDEC) SPI Flash driver
2009-06-23 7:04 ` Linus Walleij
@ 2009-06-23 20:48 ` Ryan Mallon
2009-06-24 22:43 ` Linus Walleij
0 siblings, 1 reply; 14+ messages in thread
From: Ryan Mallon @ 2009-06-23 20:48 UTC (permalink / raw)
To: Linus Walleij
Cc: David Woodhouse, linux-mtd, spi-devel-general, mike, linux kernel,
dedekind
Linus Walleij wrote:
> 2009/6/22 Ryan Mallon <ryan@bluewatersys.com>:
>
>> Out of curiosity: I'm not too clear on what makes a particular chip
>> hot-pluggable. I think technically the sst25l chip could be put onto a
>> hot-pluggable board or power domain. Can't most devices be made
>> hot-pluggable? Is the general rule to make devices non hot-pluggable if
>> most/all boards in the mainline do not allow it to be hot-plugged?
>
> I haven't seen any rule about it, but nominally the drivers support the
> configurations found in the kernel tree, so if some board physically
> existing and soon-to-run linux hotplugs chips like this, then have it
> __dev{init|exit} else __{init|exit} IMHO.
>
> Of course you can design for all plausible use cases but it will pile up
> indefinitely. Personally I try to not design software for a hardware until it
> exists, and I like the IETF catch-phrase "rough consensus and running
> code" and this sort of fits that :-)
Okay, thanks. Thats basically the conclusion we came to as well: if
someone else wants to hotplug this chip in the future they can post a
patch for it.
BTW, most of my other patches go via the ARM list, which uses the patch
system. Whats the procedure for most other lists, do patches just get
collected by the subsection maintainer when they are okay?
~Ryan
--
Bluewater Systems Ltd - ARM Technology Solution Centre
Ryan Mallon Unit 5, Amuri Park
Phone: +64 3 3779127 404 Barbadoes St
Fax: +64 3 3779135 PO Box 13 889
Email: ryan@bluewatersys.com Christchurch, 8013
Web: http://www.bluewatersys.com New Zealand
Freecall Australia 1800 148 751 USA 1800 261 2934
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH] SST25L (non JEDEC) SPI Flash driver
2009-06-23 20:48 ` Ryan Mallon
@ 2009-06-24 22:43 ` Linus Walleij
2009-06-24 22:49 ` Ryan Mallon
0 siblings, 1 reply; 14+ messages in thread
From: Linus Walleij @ 2009-06-24 22:43 UTC (permalink / raw)
To: Ryan Mallon
Cc: David Woodhouse, linux-mtd, spi-devel-general, mike, linux kernel,
dedekind
2009/6/23 Ryan Mallon <ryan@bluewatersys.com>:
> BTW, most of my other patches go via the ARM list, which uses the patch
> system. Whats the procedure for most other lists, do patches just get
> collected by the subsection maintainer when they are okay?
That's the most typical procedure, however I think the SPI maintainer
(David) has been busy with other things so I merged the PL022
driver through the ARM tree instead. Another possibility is to send
it off to Andrew Mortons tree I guess.
Yours,
Linus Walleij
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH] SST25L (non JEDEC) SPI Flash driver
2009-06-24 22:43 ` Linus Walleij
@ 2009-06-24 22:49 ` Ryan Mallon
2009-06-24 23:49 ` Linus Walleij
0 siblings, 1 reply; 14+ messages in thread
From: Ryan Mallon @ 2009-06-24 22:49 UTC (permalink / raw)
To: Linus Walleij
Cc: David Woodhouse, linux-mtd, spi-devel-general, mike, linux kernel,
dedekind
Linus Walleij wrote:
> 2009/6/23 Ryan Mallon <ryan@bluewatersys.com>:
>
>> BTW, most of my other patches go via the ARM list, which uses the patch
>> system. Whats the procedure for most other lists, do patches just get
>> collected by the subsection maintainer when they are okay?
>
> That's the most typical procedure, however I think the SPI maintainer
> (David) has been busy with other things so I merged the PL022
> driver through the ARM tree instead. Another possibility is to send
> it off to Andrew Mortons tree I guess.
Can I get your acked-by and then just CC Andrew Morton?
~Ryan
--
Bluewater Systems Ltd - ARM Technology Solution Centre
Ryan Mallon Unit 5, Amuri Park
Phone: +64 3 3779127 404 Barbadoes St
Fax: +64 3 3779135 PO Box 13 889
Email: ryan@bluewatersys.com Christchurch, 8013
Web: http://www.bluewatersys.com New Zealand
Freecall Australia 1800 148 751 USA 1800 261 2934
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH] SST25L (non JEDEC) SPI Flash driver
2009-06-24 22:49 ` Ryan Mallon
@ 2009-06-24 23:49 ` Linus Walleij
0 siblings, 0 replies; 14+ messages in thread
From: Linus Walleij @ 2009-06-24 23:49 UTC (permalink / raw)
To: Ryan Mallon
Cc: David Woodhouse, linux-mtd, spi-devel-general, mike, linux kernel,
dedekind
2009/6/25 Ryan Mallon <ryan@bluewatersys.com>:
> Linus Walleij wrote:
>> 2009/6/23 Ryan Mallon <ryan@bluewatersys.com>:
>>
>>> BTW, most of my other patches go via the ARM list, which uses the patch
>>> system. Whats the procedure for most other lists, do patches just get
>>> collected by the subsection maintainer when they are okay?
>>
>> That's the most typical procedure, however I think the SPI maintainer
>> (David) has been busy with other things so I merged the PL022
>> driver through the ARM tree instead. Another possibility is to send
>> it off to Andrew Mortons tree I guess.
>
> Can I get your acked-by and then just CC Andrew Morton?
I'm not much of a stakeholder in the SPI subsystem, but FWIW:
Acked-by: Linus Walleij <linus.walleij@stericsson.com>
Yours,
Linus Walleij
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH] SST25L (non JEDEC) SPI Flash driver
@ 2009-06-24 23:59 Ryan Mallon
2009-06-25 0:08 ` Linus Walleij
2009-06-25 2:40 ` Andrew Morton
0 siblings, 2 replies; 14+ messages in thread
From: Ryan Mallon @ 2009-06-24 23:59 UTC (permalink / raw)
To: akpm; +Cc: Linus Walleij, linux kernel, spi-devel-general, David Woodhouse
Hi Andrew,
It was suggested I send the following patch via you as David Woodhouse
is currently busy.
Thanks,
~Ryan
----
Add support for the non JEDEC SST25L SPI Flash devices.
Signed-off-by: Andre Renaud <andre@bluewatersys.com>
Signed-off-by: Ryan Mallon <ryan@bluewatersys.com>
Acked-by: Linus Walleij <linus.walleij@stericsson.com>
----
diff --git a/drivers/mtd/devices/Kconfig b/drivers/mtd/devices/Kconfig
index 325fab9..c222514 100644
--- a/drivers/mtd/devices/Kconfig
+++ b/drivers/mtd/devices/Kconfig
@@ -104,6 +104,16 @@ config M25PXX_USE_FAST_READ
help
This option enables FAST_READ access supported by ST M25Pxx.
+config MTD_SST25L
+ tristate "Support SST25L (non JEDEC) SPI Flash chips"
+ depends on SPI_MASTER
+ help
+ This enables access to the non JEDEC SST25L SPI flash chips, used
+ for program and data storage.
+
+ Set up your spi devices with the right board-specific platform data,
+ if you want to specify device partitioning.
+
config MTD_SLRAM
tristate "Uncached system RAM"
help
diff --git a/drivers/mtd/devices/Makefile b/drivers/mtd/devices/Makefile
index 0993d5c..ab5c9b9 100644
--- a/drivers/mtd/devices/Makefile
+++ b/drivers/mtd/devices/Makefile
@@ -16,3 +16,4 @@ obj-$(CONFIG_MTD_LART) += lart.o
obj-$(CONFIG_MTD_BLOCK2MTD) += block2mtd.o
obj-$(CONFIG_MTD_DATAFLASH) += mtd_dataflash.o
obj-$(CONFIG_MTD_M25P80) += m25p80.o
+obj-$(CONFIG_MTD_SST25L) += sst25l.o
diff --git a/drivers/mtd/devices/sst25l.c b/drivers/mtd/devices/sst25l.c
new file mode 100644
index 0000000..30658f6
--- /dev/null
+++ b/drivers/mtd/devices/sst25l.c
@@ -0,0 +1,503 @@
+/*
+ * drivers/mtd/sst25l.c
+ *
+ * Driver for SST25L SPI Flash chips
+ *
+ * Copyright (C) 2009 Bluewater Systems Ltd
+ * Author: Andre Renaud <andre@bluewatersys.com>
+ * Author: Ryan Mallon <ryan@bluewatersys.com>
+ *
+ * Based on m25p80.c
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/mutex.h>
+#include <linux/interrupt.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+
+#include <linux/spi/spi.h>
+#include <linux/spi/flash.h>
+
+/* Erases can take up to 3 seconds! */
+#define MAX_READY_WAIT_JIFFIES msecs_to_jiffies(3000)
+
+#define SST25L_CMD_WRSR 0x01 /* Write status register */
+#define SST25L_CMD_WRDI 0x04 /* Write disable */
+#define SST25L_CMD_RDSR 0x05 /* Read status register */
+#define SST25L_CMD_WREN 0x06 /* Write enable */
+#define SST25L_CMD_READ 0x03 /* High speed read */
+
+#define SST25L_CMD_EWSR 0x50 /* Enable write status register */
+#define SST25L_CMD_BLOCK_ERASE 0x52 /* Erase sector */
+#define SST25L_CMD_READ_ID 0x90 /* Read device ID */
+#define SST25L_CMD_AAI_PROGRAM 0xaf /* Auto address increment */
+
+#define SST25L_STATUS_BUSY (1 << 0) /* Chip is busy */
+#define SST25L_STATUS_WREN (1 << 1) /* Write enabled */
+#define SST25L_STATUS_BP0 (1 << 2) /* Block protection 0 */
+#define SST25L_STATUS_BP1 (1 << 3) /* Block protection 1 */
+
+struct sst25l_flash {
+ struct spi_device *spi;
+ struct mutex lock;
+ struct mtd_info mtd;
+
+ int partitioned;
+};
+
+struct flash_info {
+ const char *name;
+ u16 device_id;
+ unsigned page_size;
+ unsigned nr_pages;
+ unsigned erase_size;
+};
+
+#define to_sst25l_flash(x) container_of(x, struct sst25l_flash, mtd)
+
+static struct flash_info __initdata sst25l_flash_info[] = {
+ {"sst25lf020a", 0xbf43, 256, 1024, 32 * 1024},
+ {"sst25lf040a", 0xbf44, 256, 2048, 32 * 1024},
+};
+
+static int sst25l_status(struct sst25l_flash *flash, int *status)
+{
+ u8 command, response;
+ int err;
+
+ command = SST25L_CMD_RDSR;
+ err = spi_write_then_read(flash->spi, &command, 1, &response, 1);
+ if (err < 0)
+ return err;
+
+ *status = response;
+ return 0;
+}
+
+static int sst25l_write_enable(struct sst25l_flash *flash, int enable)
+{
+ u8 command[2];
+ int status, err;
+
+ command[0] = enable ? SST25L_CMD_WREN : SST25L_CMD_WRDI;
+ err = spi_write(flash->spi, command, 1);
+ if (err)
+ return err;
+
+ command[0] = SST25L_CMD_EWSR;
+ err = spi_write(flash->spi, command, 1);
+ if (err)
+ return err;
+
+ command[0] = SST25L_CMD_WRSR;
+ command[1] = enable ? 0 : SST25L_STATUS_BP0 | SST25L_STATUS_BP1;
+ err = spi_write(flash->spi, command, 2);
+ if (err)
+ return err;
+
+ if (enable) {
+ err = sst25l_status(flash, &status);
+ if (err)
+ return err;
+ if (!(status & SST25L_STATUS_WREN))
+ return -EROFS;
+ }
+
+ return 0;
+}
+
+static int sst25l_wait_till_ready(struct sst25l_flash *flash)
+{
+ unsigned long deadline;
+ int status, err;
+
+ deadline = jiffies + MAX_READY_WAIT_JIFFIES;
+ do {
+ err = sst25l_status(flash, &status);
+ if (err)
+ return err;
+ if (!(status & SST25L_STATUS_BUSY))
+ return 0;
+
+ cond_resched();
+ } while (!time_after_eq(jiffies, deadline));
+
+ return -ETIMEDOUT;
+}
+
+static int sst25l_erase_block(struct sst25l_flash *flash, u32 offset)
+{
+ u8 command[4];
+ int err;
+
+ err = sst25l_write_enable(flash, 1);
+ if (err)
+ return err;
+
+ command[0] = SST25L_CMD_BLOCK_ERASE;
+ command[1] = offset >> 16;
+ command[2] = offset >> 8;
+ command[3] = offset;
+ err = spi_write(flash->spi, command, 4);
+ if (err)
+ return err;
+
+ err = sst25l_wait_till_ready(flash);
+ if (err)
+ return err;
+
+ return sst25l_write_enable(flash, 0);
+}
+
+static int sst25l_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+ struct sst25l_flash *flash = to_sst25l_flash(mtd);
+ u32 addr, end;
+ int err;
+
+ /* Sanity checks */
+ if (instr->addr + instr->len > flash->mtd.size)
+ return -EINVAL;
+
+ if ((u32)instr->len % mtd->erasesize)
+ return -EINVAL;
+
+ if ((u32)instr->addr % mtd->erasesize)
+ return -EINVAL;
+
+ addr = instr->addr;
+ end = addr + instr->len;
+
+ mutex_lock(&flash->lock);
+
+ err = sst25l_wait_till_ready(flash);
+ if (err)
+ return err;
+
+ while (addr < end) {
+ err = sst25l_erase_block(flash, addr);
+ if (err) {
+ mutex_unlock(&flash->lock);
+ instr->state = MTD_ERASE_FAILED;
+ dev_err(&flash->spi->dev, "Erase failed\n");
+ return err;
+ }
+
+ addr += mtd->erasesize;
+ }
+
+ mutex_unlock(&flash->lock);
+
+ instr->state = MTD_ERASE_DONE;
+ mtd_erase_callback(instr);
+ return 0;
+}
+
+static int sst25l_read(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ struct sst25l_flash *flash = to_sst25l_flash(mtd);
+ struct spi_transfer transfer[2];
+ struct spi_message message;
+ u8 command[4];
+ int ret;
+
+ /* Sanity checking */
+ if (len == 0)
+ return 0;
+
+ if (from + len > flash->mtd.size)
+ return -EINVAL;
+
+ if (retlen)
+ *retlen = 0;
+
+ spi_message_init(&message);
+ memset(&transfer, 0, sizeof(transfer));
+
+ command[0] = SST25L_CMD_READ;
+ command[1] = from >> 16;
+ command[2] = from >> 8;
+ command[3] = from;
+
+ transfer[0].tx_buf = command;
+ transfer[0].len = sizeof(command);
+ spi_message_add_tail(&transfer[0], &message);
+
+ transfer[1].rx_buf = buf;
+ transfer[1].len = len;
+ spi_message_add_tail(&transfer[1], &message);
+
+ mutex_lock(&flash->lock);
+
+ /* Wait for previous write/erase to complete */
+ ret = sst25l_wait_till_ready(flash);
+ if (ret) {
+ mutex_unlock(&flash->lock);
+ return ret;
+ }
+
+ spi_sync(flash->spi, &message);
+
+ if (retlen && message.actual_length > sizeof(command))
+ *retlen += message.actual_length - sizeof(command);
+
+ mutex_unlock(&flash->lock);
+ return 0;
+}
+
+static int sst25l_write(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf)
+{
+ struct sst25l_flash *flash = to_sst25l_flash(mtd);
+ int i, j, ret, bytes, copied = 0;
+ u8 command[5];
+
+ /* Sanity checks */
+ if (!len)
+ return 0;
+
+ if (to + len > flash->mtd.size)
+ return -EINVAL;
+
+ if ((u32)to % mtd->writesize)
+ return -EINVAL;
+
+ mutex_lock(&flash->lock);
+
+ ret = sst25l_write_enable(flash, 1);
+ if (ret)
+ goto out;
+
+ for (i = 0; i < len; i += mtd->writesize) {
+ ret = sst25l_wait_till_ready(flash);
+ if (ret)
+ goto out;
+
+ /* Write the first byte of the page */
+ command[0] = SST25L_CMD_AAI_PROGRAM;
+ command[1] = (to + i) >> 16;
+ command[2] = (to + i) >> 8;
+ command[3] = (to + i);
+ command[4] = buf[i];
+ ret = spi_write(flash->spi, command, 5);
+ if (ret < 0)
+ goto out;
+ copied++;
+
+ /*
+ * Write the remaining bytes using auto address
+ * increment mode
+ */
+ bytes = min(mtd->writesize, (u32)(len - i));
+ for (j = 1; j < bytes; j++, copied++) {
+ ret = sst25l_wait_till_ready(flash);
+ if (ret)
+ goto out;
+
+ command[1] = buf[i + j];
+ ret = spi_write(flash->spi, command, 2);
+ if (ret)
+ goto out;
+ }
+ }
+
+out:
+ ret = sst25l_write_enable(flash, 0);
+
+ if (retlen)
+ *retlen = copied;
+
+ mutex_unlock(&flash->lock);
+ return ret;
+}
+
+static struct flash_info *__init sst25l_match_device(struct spi_device *spi)
+{
+ struct flash_info *flash_info = NULL;
+ u8 command[4], response;
+ int i, err;
+ u16 id;
+
+ command[0] = SST25L_CMD_READ_ID;
+ command[1] = 0;
+ command[2] = 0;
+ command[3] = 0;
+ err = spi_write_then_read(spi, command, sizeof(command), &response, 1);
+ if (err < 0) {
+ dev_err(&spi->dev, "error reading device id msb\n");
+ return NULL;
+ }
+
+ id = response << 8;
+
+ command[0] = SST25L_CMD_READ_ID;
+ command[1] = 0;
+ command[2] = 0;
+ command[3] = 1;
+ err = spi_write_then_read(spi, command, sizeof(command), &response, 1);
+ if (err < 0) {
+ dev_err(&spi->dev, "error reading device id lsb\n");
+ return NULL;
+ }
+
+ id |= response;
+
+ for (i = 0; i < ARRAY_SIZE(sst25l_flash_info); i++)
+ if (sst25l_flash_info[i].device_id == id)
+ flash_info = &sst25l_flash_info[i];
+
+ if (!flash_info)
+ dev_err(&spi->dev, "unknown id %.4x\n", id);
+
+ return flash_info;
+}
+
+static int __init sst25l_probe(struct spi_device *spi)
+{
+ struct flash_info *flash_info;
+ struct sst25l_flash *flash;
+ struct flash_platform_data *data;
+ int i;
+
+ flash_info = sst25l_match_device(spi);
+ if (!flash_info)
+ return -ENODEV;
+
+ flash = kzalloc(sizeof(struct sst25l_flash), GFP_KERNEL);
+ if (!flash)
+ return -ENOMEM;
+
+ flash->spi = spi;
+ mutex_init(&flash->lock);
+ dev_set_drvdata(&spi->dev, flash);
+
+ data = spi->dev.platform_data;
+ if (data && data->name)
+ flash->mtd.name = data->name;
+ else
+ flash->mtd.name = dev_name(&spi->dev);
+
+ flash->mtd.type = MTD_NORFLASH;
+ flash->mtd.flags = MTD_CAP_NORFLASH;
+ flash->mtd.erasesize = flash_info->erase_size;
+ flash->mtd.writesize = flash_info->page_size;
+ flash->mtd.size = flash_info->page_size * flash_info->nr_pages;
+ flash->mtd.erase = sst25l_erase;
+ flash->mtd.read = sst25l_read;
+ flash->mtd.write = sst25l_write;
+
+ dev_info(&spi->dev, "%s (%lld Kbytes)\n", flash_info->name,
+ (long long)flash->mtd.size >> 10);
+
+ DEBUG(MTD_DEBUG_LEVEL2,
+ "mtd .name = %s, .size = 0x%llx (%lldMiB) "
+ ".erasesize = 0x%.8x (%uKiB) .numeraseregions = %d\n",
+ flash->mtd.name,
+ (long long)flash->mtd.size, (long long)(flash->mtd.size >> 20),
+ flash->mtd.erasesize, flash->mtd.erasesize / 1024,
+ flash->mtd.numeraseregions);
+
+ if (flash->mtd.numeraseregions)
+ for (i = 0; i < flash->mtd.numeraseregions; i++)
+ DEBUG(MTD_DEBUG_LEVEL2,
+ "mtd.eraseregions[%d] = { .offset = 0x%llx, "
+ ".erasesize = 0x%.8x (%uKiB), "
+ ".numblocks = %d }\n",
+ i, (long long)flash->mtd.eraseregions[i].offset,
+ flash->mtd.eraseregions[i].erasesize,
+ flash->mtd.eraseregions[i].erasesize / 1024,
+ flash->mtd.eraseregions[i].numblocks);
+
+ if (mtd_has_partitions()) {
+ struct mtd_partition *parts = NULL;
+ int nr_parts = 0;
+
+ if (mtd_has_cmdlinepart()) {
+ static const char *part_probes[] =
+ {"cmdlinepart", NULL};
+
+ nr_parts = parse_mtd_partitions(&flash->mtd,
+ part_probes,
+ &parts, 0);
+ }
+
+ if (nr_parts <= 0 && data && data->parts) {
+ parts = data->parts;
+ nr_parts = data->nr_parts;
+ }
+
+ if (nr_parts > 0) {
+ for (i = 0; i < nr_parts; i++) {
+ DEBUG(MTD_DEBUG_LEVEL2, "partitions[%d] = "
+ "{.name = %s, .offset = 0x%llx, "
+ ".size = 0x%llx (%lldKiB) }\n",
+ i, parts[i].name,
+ (long long)parts[i].offset,
+ (long long)parts[i].size,
+ (long long)(parts[i].size >> 10));
+ }
+
+ flash->partitioned = 1;
+ return add_mtd_partitions(&flash->mtd,
+ parts, nr_parts);
+ }
+
+ } else if (data->nr_parts) {
+ dev_warn(&spi->dev, "ignoring %d default partitions on %s\n",
+ data->nr_parts, data->name);
+ }
+
+ return add_mtd_device(&flash->mtd) == 1 ? -ENODEV : 0;
+}
+
+static int __exit sst25l_remove(struct spi_device *spi)
+{
+ struct sst25l_flash *flash = dev_get_drvdata(&spi->dev);
+ int ret;
+
+ if (mtd_has_partitions() && flash->partitioned)
+ ret = del_mtd_partitions(&flash->mtd);
+ else
+ ret = del_mtd_device(&flash->mtd);
+ if (ret == 0)
+ kfree(flash);
+ return ret;
+}
+
+static struct spi_driver sst25l_driver = {
+ .driver = {
+ .name = "sst25l",
+ .bus = &spi_bus_type,
+ .owner = THIS_MODULE,
+ },
+ .probe = sst25l_probe,
+ .remove = __exit_p(sst25l_remove),
+};
+
+static int __init sst25l_init(void)
+{
+ return spi_register_driver(&sst25l_driver);
+}
+
+static void __exit sst25l_exit(void)
+{
+ spi_unregister_driver(&sst25l_driver);
+}
+
+module_init(sst25l_init);
+module_exit(sst25l_exit);
+
+MODULE_DESCRIPTION("MTD SPI driver for SST25L Flash chips");
+MODULE_AUTHOR("Andre Renaud <andre@bluewatersys.com>, "
+ "Ryan Mallon <ryan@bluewatersys.com>");
+MODULE_LICENSE("GPL");
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH] SST25L (non JEDEC) SPI Flash driver
2009-06-24 23:59 [PATCH] SST25L (non JEDEC) SPI Flash driver Ryan Mallon
@ 2009-06-25 0:08 ` Linus Walleij
2009-07-03 0:50 ` [spi-devel-general] " David Brownell
2009-06-25 2:40 ` Andrew Morton
1 sibling, 1 reply; 14+ messages in thread
From: Linus Walleij @ 2009-06-25 0:08 UTC (permalink / raw)
To: Ryan Mallon; +Cc: akpm, linux kernel, spi-devel-general, David Woodhouse
2009/6/25 Ryan Mallon <ryan@bluewatersys.com>:
> It was suggested I send the following patch via you as David Woodhouse
> is currently busy.
Actually it was David Brownell (the SPI maintainer) I was thinking of
that was sort of busy lately, perhaps Woodhouse (the MTD maintainer)
is less busy ... let's see.
Linus Walleij
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH] SST25L (non JEDEC) SPI Flash driver
2009-06-24 23:59 [PATCH] SST25L (non JEDEC) SPI Flash driver Ryan Mallon
2009-06-25 0:08 ` Linus Walleij
@ 2009-06-25 2:40 ` Andrew Morton
2009-06-25 3:07 ` Ryan Mallon
1 sibling, 1 reply; 14+ messages in thread
From: Andrew Morton @ 2009-06-25 2:40 UTC (permalink / raw)
To: Ryan Mallon
Cc: Linus Walleij, linux kernel, spi-devel-general, David Woodhouse
On Thu, 25 Jun 2009 11:59:47 +1200 Ryan Mallon <ryan@bluewatersys.com> wrote:
> Add support for the non JEDEC SST25L SPI Flash devices.
>
> Signed-off-by: Andre Renaud <andre@bluewatersys.com>
> Signed-off-by: Ryan Mallon <ryan@bluewatersys.com>
> Acked-by: Linus Walleij <linus.walleij@stericsson.com>
It looks OK to my inexperienced eye.
> ...
>
> + bytes = min(mtd->writesize, (u32)(len - i));
The conventional way of suppressing the warning is min_t().
> +static int __init sst25l_probe(struct spi_device *spi)
> +{
> + struct flash_info *flash_info;
> + struct sst25l_flash *flash;
> + struct flash_platform_data *data;
> + int i;
> +
> + flash_info = sst25l_match_device(spi);
> + if (!flash_info)
> + return -ENODEV;
> +
> + flash = kzalloc(sizeof(struct sst25l_flash), GFP_KERNEL);
> + if (!flash)
> + return -ENOMEM;
> +
> + flash->spi = spi;
> + mutex_init(&flash->lock);
> + dev_set_drvdata(&spi->dev, flash);
> +
> + data = spi->dev.platform_data;
> + if (data && data->name)
> + flash->mtd.name = data->name;
> + else
> + flash->mtd.name = dev_name(&spi->dev);
> +
> + flash->mtd.type = MTD_NORFLASH;
> + flash->mtd.flags = MTD_CAP_NORFLASH;
> + flash->mtd.erasesize = flash_info->erase_size;
> + flash->mtd.writesize = flash_info->page_size;
> + flash->mtd.size = flash_info->page_size * flash_info->nr_pages;
> + flash->mtd.erase = sst25l_erase;
> + flash->mtd.read = sst25l_read;
> + flash->mtd.write = sst25l_write;
> +
> + dev_info(&spi->dev, "%s (%lld Kbytes)\n", flash_info->name,
> + (long long)flash->mtd.size >> 10);
> +
> + DEBUG(MTD_DEBUG_LEVEL2,
> + "mtd .name = %s, .size = 0x%llx (%lldMiB) "
> + ".erasesize = 0x%.8x (%uKiB) .numeraseregions = %d\n",
> + flash->mtd.name,
> + (long long)flash->mtd.size, (long long)(flash->mtd.size >> 20),
> + flash->mtd.erasesize, flash->mtd.erasesize / 1024,
> + flash->mtd.numeraseregions);
> +
> + if (flash->mtd.numeraseregions)
> + for (i = 0; i < flash->mtd.numeraseregions; i++)
> + DEBUG(MTD_DEBUG_LEVEL2,
> + "mtd.eraseregions[%d] = { .offset = 0x%llx, "
> + ".erasesize = 0x%.8x (%uKiB), "
> + ".numblocks = %d }\n",
> + i, (long long)flash->mtd.eraseregions[i].offset,
> + flash->mtd.eraseregions[i].erasesize,
> + flash->mtd.eraseregions[i].erasesize / 1024,
> + flash->mtd.eraseregions[i].numblocks);
> +
> + if (mtd_has_partitions()) {
> + struct mtd_partition *parts = NULL;
> + int nr_parts = 0;
> +
> + if (mtd_has_cmdlinepart()) {
> + static const char *part_probes[] =
> + {"cmdlinepart", NULL};
> +
> + nr_parts = parse_mtd_partitions(&flash->mtd,
> + part_probes,
> + &parts, 0);
> + }
> +
> + if (nr_parts <= 0 && data && data->parts) {
> + parts = data->parts;
> + nr_parts = data->nr_parts;
> + }
> +
> + if (nr_parts > 0) {
> + for (i = 0; i < nr_parts; i++) {
> + DEBUG(MTD_DEBUG_LEVEL2, "partitions[%d] = "
> + "{.name = %s, .offset = 0x%llx, "
> + ".size = 0x%llx (%lldKiB) }\n",
> + i, parts[i].name,
> + (long long)parts[i].offset,
> + (long long)parts[i].size,
> + (long long)(parts[i].size >> 10));
> + }
> +
> + flash->partitioned = 1;
> + return add_mtd_partitions(&flash->mtd,
> + parts, nr_parts);
> + }
> +
> + } else if (data->nr_parts) {
> + dev_warn(&spi->dev, "ignoring %d default partitions on %s\n",
> + data->nr_parts, data->name);
> + }
> +
> + return add_mtd_device(&flash->mtd) == 1 ? -ENODEV : 0;
If this returns -ENODEV, did we leak the memory at *flash?
> +}
> +
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH] SST25L (non JEDEC) SPI Flash driver
2009-06-25 2:40 ` Andrew Morton
@ 2009-06-25 3:07 ` Ryan Mallon
0 siblings, 0 replies; 14+ messages in thread
From: Ryan Mallon @ 2009-06-25 3:07 UTC (permalink / raw)
To: Andrew Morton
Cc: Linus Walleij, linux kernel, spi-devel-general, David Woodhouse
Andrew Morton wrote:
> On Thu, 25 Jun 2009 11:59:47 +1200 Ryan Mallon <ryan@bluewatersys.com> wrote:
>
>
>> Add support for the non JEDEC SST25L SPI Flash devices.
>>
>> Signed-off-by: Andre Renaud <andre@bluewatersys.com>
>> Signed-off-by: Ryan Mallon <ryan@bluewatersys.com>
>> Acked-by: Linus Walleij <linus.walleij@stericsson.com>
>>
>
> It looks OK to my inexperienced eye.
>
>
Cool, thanks for picking this up. The patch below fixes the two issues
you pointed out. It will apply on top of
mtd-sst25l-non-jedec-spi-flash-driver-update.patch
----
Fix two issues in the SSTL25 driver:
- Replace min() plus u32 cast with min_t
- Fix memory leak on failed add_mtd_device
Signed-off-by: Ryan Mallon <ryan@bluewatersys.com>
----
diff --git a/drivers/mtd/devices/sst25l.c b/drivers/mtd/devices/sst25l.c
index 777fadf..d235067 100644
--- a/drivers/mtd/devices/sst25l.c
+++ b/drivers/mtd/devices/sst25l.c
@@ -298,7 +298,7 @@ static int sst25l_write(struct mtd_info *mtd, loff_t to, size_t len,
* Write the remaining bytes using auto address
* increment mode
*/
- bytes = min(mtd->writesize, (u32)(len - i));
+ bytes = min_t(u32, mtd->writesize, len - i);
for (j = 1; j < bytes; j++, copied++) {
ret = sst25l_wait_till_ready(flash);
if (ret)
@@ -367,7 +367,7 @@ static int __init sst25l_probe(struct spi_device *spi)
struct flash_info *flash_info;
struct sst25l_flash *flash;
struct flash_platform_data *data;
- int i;
+ int ret, i;
flash_info = sst25l_match_device(spi);
if (!flash_info)
@@ -457,7 +457,14 @@ static int __init sst25l_probe(struct spi_device *spi)
data->nr_parts, data->name);
}
- return add_mtd_device(&flash->mtd) == 1 ? -ENODEV : 0;
+ ret = add_mtd_device(&flash->mtd);
+ if (ret == 1) {
+ kfree(flash);
+ dev_set_drvdata(&spi->dev, NULL);
+ return -ENODEV;
+ }
+
+ return 0;
}
static int __exit sst25l_remove(struct spi_device *spi)
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [spi-devel-general] [PATCH] SST25L (non JEDEC) SPI Flash driver
2009-06-25 0:08 ` Linus Walleij
@ 2009-07-03 0:50 ` David Brownell
0 siblings, 0 replies; 14+ messages in thread
From: David Brownell @ 2009-07-03 0:50 UTC (permalink / raw)
To: spi-devel-general
Cc: Linus Walleij, Ryan Mallon, akpm, David Woodhouse, linux kernel
On Wednesday 24 June 2009, Linus Walleij wrote:
> > It was suggested I send the following patch via you as David Woodhouse
> > is currently busy.
>
> Actually it was David Brownell (the SPI maintainer) I was thinking of
> that was sort of busy lately, perhaps Woodhouse (the MTD maintainer)
> is less busy ... let's see.
It's an MTD patch so I'd expect it to go through the MTD tree... :)
^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2009-07-03 0:50 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2009-06-24 23:59 [PATCH] SST25L (non JEDEC) SPI Flash driver Ryan Mallon
2009-06-25 0:08 ` Linus Walleij
2009-07-03 0:50 ` [spi-devel-general] " David Brownell
2009-06-25 2:40 ` Andrew Morton
2009-06-25 3:07 ` Ryan Mallon
-- strict thread matches above, loose matches on Subject: below --
2009-06-22 3:58 Ryan Mallon
2009-06-22 8:15 ` Artem Bityutskiy
2009-06-22 8:22 ` Linus Walleij
2009-06-22 21:56 ` Ryan Mallon
2009-06-23 7:04 ` Linus Walleij
2009-06-23 20:48 ` Ryan Mallon
2009-06-24 22:43 ` Linus Walleij
2009-06-24 22:49 ` Ryan Mallon
2009-06-24 23:49 ` Linus Walleij
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox