alistair23-linux/drivers/mtd/devices/sst25l.c
Linus Torvalds 02f0d3f758 MTD updates for 4.4-rc1:
Core
 
   * WARN (in some cases) when a struct mtd_info is registered multiple times;
     in the past this was "supported", but it's still error prone for future
     development. There's only one ugly case of this left in the tree (that
     we're aware of) and the owners are aware of the problems there.
 
   * fix potential deadlock in the blkdev removal path
     NOTE: the (potential) deadlock was introduced in a for-stable patch. This
     one is also marked for -stable.
 
   * ioctl(BLKPG) compat_ioctl support; resolves issues with 32-bit user space
     vs. 64-bit kernel space
 
   * Set MTD parent device correctly throughout the tree, so the tree structure
     appears correctly in sysfs; many drivers were missing this (soft)
     requirement
 
   * Move device tree partitions (ofpart) into a dedicated 'partitions' subnode;
     this helps to disambiguate whether a node is a partition or some other
     auxiliary data
 
   * Improve error handling for partitioning failures
 
  NAND
 
   * General: Increase timeout period, for corner-case systems with
     less-than-accurate jiffies
 
   * Fix OF-based autoloading of several NAND drivers when built as modules
 
   * pxa3xx_nand:
     - Rework timing configuration to be more dynamic
     - Refactor PM support
 
   * brcmnand: prepare for NorthStar 2 support (ARM64, 16-bit NAND chips)
 
   * sunxi_nand: refactoring and a few bug fixes
 
   * vf610: new NAND driver
 
   * FSMC: add SW BCH support; support common NAND DT bindings
 
   * lpc32xx_slc: refactor and improve timing calculations logic
 
   * denali: support for rev 5.1
 
  SPI NOR
 
   * Layering improvements
 
   * Added Winbond lock/unlock support
 
   * Added mtd_is_locked() (i.e., ioctl(MEMISLOCKED)) support
 
   * Increase full-chip-erase timeout linearly with flash size
 
   * fsl-quadspi: fix compile for non-ARM architectures
 
   * New flash support
 -----BEGIN PGP SIGNATURE-----
 Version: GnuPG v1
 
 iQIcBAABAgAGBQJWPPj0AAoJEFySrpd9RFgtJEgP/RnfXRHBX51cUl4r8XrxiGtz
 zlH5zFOCYWEOtlGjoD25wp+A5RRjWi62At6KmLJZncf0clJ65fyKHjt/JEg6YuI6
 DUTMepTwyC2Wh7Ux1ZEH3KOnl64xh5p+Wf7Tl4yUr0DCd26VEwE9o7tPRlahv9nx
 OhGCWS+uSsxW0Q2wDLCypFzXsnDBeoGxDO7VCIzle4d3aJ4PCcQIXINlr6ZYpocT
 VTTadPCMsmYq6AgV5W3KYGYLj62ITiN2YxdJRacg+QkKLfl21u2wVy9Ahk/oj5TG
 bV0DHiz6ky1F2K/bvJibcRFABiYup9UXIo1IzwLloSxEJ/GfennMC29xn9K/0gid
 e2kO/Ajh/bihgJXzoAoZD/40YJK40X5VW9rQZgo482sMBGLmJOzZiOHOIEXaVohs
 djQ7sbCwPmLwqp7C+6WTn8frp6ntIe9iXdmjDuR/WlPP0Sh5rI3cUeBQrJXEYxwc
 aYt1Zxkst6gEMPQJ/S2TiKwWm0BxVWEUEjKmt9FPWOaQnwmoeju0Y/6jIvs1TQKM
 vO8cmS17QyPUWT2kGIDmZ51KayY26uDC8/NA2t1HDYCiFbpDp61kgu2wyoBUTg7q
 YIAvjtnwOaG9qk0SLfZu4FEgNi4a/bC1bxGxChsi+S2krpNQAeMlPY394cf6OdsE
 j+CV/Ko0DguO26bO0MWr
 =UzSW
 -----END PGP SIGNATURE-----

Merge tag 'for-linus-20151106' of git://git.infradead.org/linux-mtd

Pull MTD updates from Brian Norris:
 "Core:

   - WARN (in some cases) when a struct mtd_info is registered multiple
     times; in the past this was "supported", but it's still error prone
     for future development.  There's only one ugly case of this left in
     the tree (that we're aware of) and the owners are aware of the
     problems there.

   - fix potential deadlock in the blkdev removal path NOTE: the
     (potential) deadlock was introduced in a for-stable patch.  This
     one is also marked for -stable.

   - ioctl(BLKPG) compat_ioctl support; resolves issues with 32-bit user
     space vs 64-bit kernel space

   - Set MTD parent device correctly throughout the tree, so the tree
     structure appears correctly in sysfs; many drivers were missing
     this (soft) requirement

   - Move device tree partitions (ofpart) into a dedicated 'partitions'
     subnode; this helps to disambiguate whether a node is a partition
     or some other auxiliary data

   - Improve error handling for partitioning failures

  NAND:

   - General: Increase timeout period, for corner-case systems with
     less-than-accurate jiffies

   - Fix OF-based autoloading of several NAND drivers when built as
     modules

   - pxa3xx_nand:
      - Rework timing configuration to be more dynamic
      - Refactor PM support

   - brcmnand: prepare for NorthStar 2 support (ARM64, 16-bit NAND
     chips)

   - sunxi_nand: refactoring and a few bug fixes

   - vf610: new NAND driver

   - FSMC: add SW BCH support; support common NAND DT bindings

   - lpc32xx_slc: refactor and improve timing calculations logic

   - denali: support for rev 5.1

  SPI NOR:

   - Layering improvements

   - Added Winbond lock/unlock support

   - Added mtd_is_locked() (i.e., ioctl(MEMISLOCKED)) support

   - Increase full-chip-erase timeout linearly with flash size

   - fsl-quadspi: fix compile for non-ARM architectures

   - New flash support"

* tag 'for-linus-20151106' of git://git.infradead.org/linux-mtd: (169 commits)
  mtd: don't WARN about overloaded users of mtd->reboot_notifier.notifier_call
  mtd: nand: sunxi: avoid retrieving data before ECC pass
  mtd: nand: sunxi: fix sunxi_nfc_hw_ecc_read/write_chunk()
  mtd: blkdevs: fix potential deadlock + lockdep warnings
  mtd: ofpart: move ofpart partitions to a dedicated dt node
  doc: dt: mtd: support partitions in a special 'partitions' subnode
  mtd: brcmnand: Force 8bit mode before doing nand_scan_ident()
  mtd: brcmnand: factor out CFG and CFG_EXT bitfields
  mtd: mtdpart: Do not fail mtd probe when parsing partitions fails
  mtd: fsl-quadspi: fix macro collision problems with READ/WRITE
  mtd: warn when registering the same master many times
  mtd: fixup corner case error handling in mtd_device_parse_register()
  mtd: tests: Replace timeval with ktime_t
  mtd: fsmc_nand: Add BCH4 SW ECC support for SPEAr600
  mtd: nand: vf610_nfc: use nand_check_erased_ecc_chunk() helper
  mtd: nand: increase ready wait timeout and report timeouts
  mtd: docg3: off by one in doc_register_sysfs()
  mtd: pxa3xx_nand: clean up the pxa3xx timings
  mtd: pxa3xx_nand: rework flash detection and timing setup
  mtd: pxa3xx_nand: add helpers to setup the timings
  ...
2015-11-06 11:50:24 -08:00

430 lines
9.6 KiB
C

/*
* sst25l.c
*
* Driver for SST25L SPI Flash chips
*
* Copyright © 2009 Bluewater Systems Ltd
* Author: Andre Renaud <andre@bluewatersys.com>
* Author: Ryan Mallon
*
* 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/module.h>
#include <linux/device.h>
#include <linux/mutex.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/sched.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_SECTOR_ERASE 0x20 /* 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;
};
struct flash_info {
const char *name;
uint16_t 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 sst25l_flash_info[] = {
{"sst25lf020a", 0xbf43, 256, 1024, 4096},
{"sst25lf040a", 0xbf44, 256, 2048, 4096},
};
static int sst25l_status(struct sst25l_flash *flash, int *status)
{
struct spi_message m;
struct spi_transfer t;
unsigned char cmd_resp[2];
int err;
spi_message_init(&m);
memset(&t, 0, sizeof(struct spi_transfer));
cmd_resp[0] = SST25L_CMD_RDSR;
cmd_resp[1] = 0xff;
t.tx_buf = cmd_resp;
t.rx_buf = cmd_resp;
t.len = sizeof(cmd_resp);
spi_message_add_tail(&t, &m);
err = spi_sync(flash->spi, &m);
if (err < 0)
return err;
*status = cmd_resp[1];
return 0;
}
static int sst25l_write_enable(struct sst25l_flash *flash, int enable)
{
unsigned char 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_sector(struct sst25l_flash *flash, uint32_t offset)
{
unsigned char command[4];
int err;
err = sst25l_write_enable(flash, 1);
if (err)
return err;
command[0] = SST25L_CMD_SECTOR_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);
uint32_t addr, end;
int err;
/* Sanity checks */
if ((uint32_t)instr->len % mtd->erasesize)
return -EINVAL;
if ((uint32_t)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) {
mutex_unlock(&flash->lock);
return err;
}
while (addr < end) {
err = sst25l_erase_sector(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, unsigned char *buf)
{
struct sst25l_flash *flash = to_sst25l_flash(mtd);
struct spi_transfer transfer[2];
struct spi_message message;
unsigned char command[4];
int ret;
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 unsigned char *buf)
{
struct sst25l_flash *flash = to_sst25l_flash(mtd);
int i, j, ret, bytes, copied = 0;
unsigned char command[5];
if ((uint32_t)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_t(uint32_t, 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 *sst25l_match_device(struct spi_device *spi)
{
struct flash_info *flash_info = NULL;
struct spi_message m;
struct spi_transfer t;
unsigned char cmd_resp[6];
int i, err;
uint16_t id;
spi_message_init(&m);
memset(&t, 0, sizeof(struct spi_transfer));
cmd_resp[0] = SST25L_CMD_READ_ID;
cmd_resp[1] = 0;
cmd_resp[2] = 0;
cmd_resp[3] = 0;
cmd_resp[4] = 0xff;
cmd_resp[5] = 0xff;
t.tx_buf = cmd_resp;
t.rx_buf = cmd_resp;
t.len = sizeof(cmd_resp);
spi_message_add_tail(&t, &m);
err = spi_sync(spi, &m);
if (err < 0) {
dev_err(&spi->dev, "error reading device id\n");
return NULL;
}
id = (cmd_resp[4] << 8) | cmd_resp[5];
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 sst25l_probe(struct spi_device *spi)
{
struct flash_info *flash_info;
struct sst25l_flash *flash;
struct flash_platform_data *data;
int ret;
flash_info = sst25l_match_device(spi);
if (!flash_info)
return -ENODEV;
flash = devm_kzalloc(&spi->dev, sizeof(*flash), GFP_KERNEL);
if (!flash)
return -ENOMEM;
flash->spi = spi;
mutex_init(&flash->lock);
spi_set_drvdata(spi, flash);
data = dev_get_platdata(&spi->dev);
if (data && data->name)
flash->mtd.name = data->name;
flash->mtd.dev.parent = &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.writebufsize = 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 KiB)\n", flash_info->name,
(long long)flash->mtd.size >> 10);
pr_debug("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);
ret = mtd_device_parse_register(&flash->mtd, NULL, NULL,
data ? data->parts : NULL,
data ? data->nr_parts : 0);
if (ret)
return -ENODEV;
return 0;
}
static int sst25l_remove(struct spi_device *spi)
{
struct sst25l_flash *flash = spi_get_drvdata(spi);
return mtd_device_unregister(&flash->mtd);
}
static struct spi_driver sst25l_driver = {
.driver = {
.name = "sst25l",
},
.probe = sst25l_probe,
.remove = sst25l_remove,
};
module_spi_driver(sst25l_driver);
MODULE_DESCRIPTION("MTD SPI driver for SST25L Flash chips");
MODULE_AUTHOR("Andre Renaud <andre@bluewatersys.com>, "
"Ryan Mallon");
MODULE_LICENSE("GPL");