This patch adds driver for Cypress FRAMs on SPI bus, such as FM25V05, FM25V10
etc.
Reworked from at25 driver:
- simplified writing since data are written without erasing and waiting to
finish write cycle
- add reading device ID and choose size and addr len from it
- add serial number reading and exporting it to sysfs

Signed-off-by: Jiri Prchal <[email protected]>
---
 drivers/misc/eeprom/Kconfig  |  11 +
 drivers/misc/eeprom/Makefile |   1 +
 drivers/misc/eeprom/fm25.c   | 500 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 512 insertions(+)

diff --git a/drivers/misc/eeprom/Kconfig b/drivers/misc/eeprom/Kconfig
index 9536852f..aee6a73 100644
--- a/drivers/misc/eeprom/Kconfig
+++ b/drivers/misc/eeprom/Kconfig
@@ -38,6 +38,17 @@ config EEPROM_AT25
          This driver can also be built as a module.  If so, the module
          will be called at25.
 
+config FRAM_FM25
+       tristate "SPI Cypress FRAM"
+       depends on SPI && SYSFS
+       help
+         Enable this driver to get read/write support to SPI FRAMs,
+         after you configure the board init code to know about each fram
+         on your target board.
+
+         This driver can also be built as a module.  If so, the module
+         will be called fm25.
+
 config EEPROM_LEGACY
        tristate "Old I2C EEPROM reader"
        depends on I2C && SYSFS
diff --git a/drivers/misc/eeprom/Makefile b/drivers/misc/eeprom/Makefile
index 9507aec..6738752 100644
--- a/drivers/misc/eeprom/Makefile
+++ b/drivers/misc/eeprom/Makefile
@@ -1,5 +1,6 @@
 obj-$(CONFIG_EEPROM_AT24)      += at24.o
 obj-$(CONFIG_EEPROM_AT25)      += at25.o
+obj-$(CONFIG_FRAM_FM25)                += fm25.o
 obj-$(CONFIG_EEPROM_LEGACY)    += eeprom.o
 obj-$(CONFIG_EEPROM_MAX6875)   += max6875.o
 obj-$(CONFIG_EEPROM_93CX6)     += eeprom_93cx6.o
diff --git a/drivers/misc/eeprom/fm25.c b/drivers/misc/eeprom/fm25.c
new file mode 100644
index 0000000..6a33b4d
--- /dev/null
+++ b/drivers/misc/eeprom/fm25.c
@@ -0,0 +1,500 @@
+/*
+ * fm25.c -- support SPI FRAMs, such as Cypress FM25 models
+ *
+ * Copyright (C) 2014 Jiri Prchal
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/sched.h>
+
+#include <linux/spi/spi.h>
+#include <linux/spi/eeprom.h>
+#include <linux/of.h>
+
+struct fm25_data {
+       struct spi_device       *spi;
+       struct memory_accessor  mem;
+       struct mutex            lock;
+       struct spi_eeprom       chip;
+       struct bin_attribute    bin;
+       unsigned                addrlen;
+       int                     has_sernum;
+};
+
+#define        FM25_WREN       0x06            /* latch the write enable */
+#define        FM25_WRDI       0x04            /* reset the write enable */
+#define        FM25_RDSR       0x05            /* read status register */
+#define        FM25_WRSR       0x01            /* write status register */
+#define        FM25_READ       0x03            /* read byte(s) */
+#define        FM25_WRITE      0x02            /* write byte(s)/sector */
+#define        FM25_SLEEP      0xb9            /* enter sleep mode */
+#define        FM25_RDID       0x9f            /* read device ID */
+#define        FM25_RDSN       0xc3            /* read S/N */
+
+#define        FM25_SR_WEN     0x02            /* write enable (latched) */
+#define        FM25_SR_BP0     0x04            /* BP for software writeprotect 
*/
+#define        FM25_SR_BP1     0x08
+#define        FM25_SR_WPEN    0x80            /* writeprotect enable */
+
+#define FM25_ID_LEN    9               /* ID lenght */
+#define FM25_SN_LEN    8               /* serial number lenght */
+
+#define FM25_MAXADDRLEN        3               /* 24 bit addresses */
+
+#define        io_limit        PAGE_SIZE       /* bytes */
+
+static ssize_t
+fm25_data_read(
+       struct fm25_data        *fm25,
+       char                    *buf,
+       unsigned                offset,
+       size_t                  count
+)
+{
+       u8                      command[FM25_MAXADDRLEN + 1];
+       u8                      *cp;
+       ssize_t                 status;
+       struct spi_transfer     t[2];
+       struct spi_message      m;
+       u8                      instr;
+
+       if (unlikely(offset >= fm25->bin.size))
+               return 0;
+       if ((offset + count) > fm25->bin.size)
+               count = fm25->bin.size - offset;
+       if (unlikely(!count))
+               return count;
+
+       cp = command;
+
+       instr = FM25_READ;
+       *cp++ = instr;
+
+       /* 8/16/24-bit address is written MSB first */
+       switch (fm25->addrlen) {
+       default:        /* case 3 */
+               *cp++ = offset >> 16;
+       case 2:
+               *cp++ = offset >> 8;
+       case 1:
+       case 0: /* can't happen: for better codegen */
+               *cp++ = offset >> 0;
+       }
+
+       spi_message_init(&m);
+       memset(t, 0, sizeof t);
+
+       t[0].tx_buf = command;
+       t[0].len = fm25->addrlen + 1;
+       spi_message_add_tail(&t[0], &m);
+
+       t[1].rx_buf = buf;
+       t[1].len = count;
+       spi_message_add_tail(&t[1], &m);
+
+       mutex_lock(&fm25->lock);
+
+       /* Read it all at once.
+        *
+        * REVISIT that's potentially a problem with large chips, if
+        * other devices on the bus need to be accessed regularly or
+        * this chip is clocked very slowly
+        */
+       status = spi_sync(fm25->spi, &m);
+       dev_dbg(&fm25->spi->dev,
+               "read %Zd bytes at %d --> %d\n",
+               count, offset, (int) status);
+
+       mutex_unlock(&fm25->lock);
+       return status ? status : count;
+}
+
+static ssize_t
+fm25_id_read(struct fm25_data *fm25, char *buf)
+{
+       u8                      command = FM25_RDID;
+       ssize_t                 status;
+       struct spi_transfer     t[2];
+       struct spi_message      m;
+
+       spi_message_init(&m);
+       memset(t, 0, sizeof t);
+
+       t[0].tx_buf = &command;
+       t[0].len = 1;
+       spi_message_add_tail(&t[0], &m);
+
+       t[1].rx_buf = buf;
+       t[1].len = FM25_ID_LEN;
+       spi_message_add_tail(&t[1], &m);
+
+       mutex_lock(&fm25->lock);
+
+       status = spi_sync(fm25->spi, &m);
+       dev_dbg(&fm25->spi->dev,
+               "read %Zd bytes of ID --> %d\n",
+        FM25_ID_LEN, (int) status);
+
+       mutex_unlock(&fm25->lock);
+       return status ? status : FM25_ID_LEN;
+}
+
+static ssize_t
+fm25_sernum_read(struct fm25_data *fm25, char *buf)
+{
+       u8                      command = FM25_RDSN;
+       ssize_t                 status;
+       struct spi_transfer     t[2];
+       struct spi_message      m;
+
+       spi_message_init(&m);
+       memset(t, 0, sizeof t);
+
+       t[0].tx_buf = &command;
+       t[0].len = 1;
+       spi_message_add_tail(&t[0], &m);
+
+       t[1].rx_buf = buf;
+       t[1].len = FM25_SN_LEN;
+       spi_message_add_tail(&t[1], &m);
+
+       mutex_lock(&fm25->lock);
+
+       status = spi_sync(fm25->spi, &m);
+       dev_dbg(&fm25->spi->dev,
+               "read %Zd bytes of serial number --> %d\n",
+               FM25_SN_LEN, (int) status);
+
+       mutex_unlock(&fm25->lock);
+       return status ? status : FM25_SN_LEN;
+}
+
+static ssize_t
+sernum_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       char                    binbuf[FM25_SN_LEN];
+       struct fm25_data        *fm25;
+       int                     i;
+       char                    *pbuf = buf;
+
+       fm25 = dev_get_drvdata(dev);
+       fm25_sernum_read(fm25, binbuf);
+       for (i = 0; i < FM25_SN_LEN; i++)
+               pbuf += sprintf(pbuf, "%02x ", binbuf[i]);
+       sprintf(--pbuf, "\n");
+       return (3 * i);
+}
+static const DEVICE_ATTR_RO(sernum);
+
+static ssize_t
+fm25_bin_read(struct file *filp, struct kobject *kobj,
+             struct bin_attribute *bin_attr,
+             char *buf, loff_t off, size_t count)
+{
+       struct device           *dev;
+       struct fm25_data        *fm25;
+
+       dev = container_of(kobj, struct device, kobj);
+       fm25 = dev_get_drvdata(dev);
+
+       return fm25_data_read(fm25, buf, off, count);
+}
+
+
+static ssize_t
+fm25_data_write(struct fm25_data *fm25, const char *buf, loff_t off,
+             size_t count)
+{
+       ssize_t                 status = 0;
+       unsigned                written = 0;
+       unsigned                buf_size;
+       u8                      *bounce;
+
+       if (unlikely(off >= fm25->bin.size))
+               return -EFBIG;
+       if ((off + count) > fm25->bin.size)
+               count = fm25->bin.size - off;
+       if (unlikely(!count))
+               return count;
+
+       /* Temp buffer starts with command and address */
+       buf_size = io_limit;
+       bounce = kmalloc(buf_size + fm25->addrlen + 1, GFP_KERNEL);
+       if (!bounce)
+               return -ENOMEM;
+
+       /* For write, rollover is within the page ... so we write at
+        * most one page, then manually roll over to the next page.
+        */
+       mutex_lock(&fm25->lock);
+       do {
+               unsigned        segment;
+               unsigned        offset = (unsigned) off;
+               u8              *cp = bounce;
+               u8              instr;
+
+               *cp = FM25_WREN;
+               status = spi_write(fm25->spi, cp, 1);
+               if (status < 0) {
+                       dev_dbg(&fm25->spi->dev, "WREN --> %d\n",
+                                       (int) status);
+                       break;
+               }
+
+               instr = FM25_WRITE;
+               *cp++ = instr;
+
+               /* 8/16/24-bit address is written MSB first */
+               switch (fm25->addrlen) {
+               default:        /* case 3 */
+                       *cp++ = offset >> 16;
+               case 2:
+                       *cp++ = offset >> 8;
+               case 1:
+               case 0: /* can't happen: for better codegen */
+                       *cp++ = offset >> 0;
+               }
+
+               /* Write as much of a page as we can */
+               segment = buf_size - (offset % buf_size);
+               if (segment > count)
+                       segment = count;
+               memcpy(cp, buf, segment);
+               status = spi_write(fm25->spi, bounce,
+                               segment + fm25->addrlen + 1);
+               dev_dbg(&fm25->spi->dev,
+                               "write %u bytes at %u --> %d\n",
+                               segment, offset, (int) status);
+               if (status < 0)
+                       break;
+
+               /* REVISIT this should detect (or prevent) failed writes
+                * to readonly sections of the EEPROM...
+                */
+
+               off += segment;
+               buf += segment;
+               count -= segment;
+               written += segment;
+
+       } while (count > 0);
+
+       mutex_unlock(&fm25->lock);
+
+       kfree(bounce);
+       return written ? written : status;
+}
+
+static ssize_t
+fm25_bin_write(struct file *filp, struct kobject *kobj,
+              struct bin_attribute *bin_attr,
+              char *buf, loff_t off, size_t count)
+{
+       struct device           *dev;
+       struct fm25_data        *fm25;
+
+       dev = container_of(kobj, struct device, kobj);
+       fm25 = dev_get_drvdata(dev);
+
+       return fm25_data_write(fm25, buf, off, count);
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* Let in-kernel code access the eeprom data. */
+
+static ssize_t fm25_mem_read(struct memory_accessor *mem, char *buf,
+                            off_t offset, size_t count)
+{
+  struct fm25_data *fm25 = container_of(mem, struct fm25_data, mem);
+
+  return fm25_data_read(fm25, buf, offset, count);
+}
+
+static ssize_t fm25_mem_write(struct memory_accessor *mem, const char *buf,
+                         off_t offset, size_t count)
+{
+       struct fm25_data *fm25 = container_of(mem, struct fm25_data, mem);
+
+       return fm25_data_write(fm25, buf, offset, count);
+}
+
+/*-------------------------------------------------------------------------*/
+
+static int fm25_np_to_chip(struct device *dev,
+                          struct device_node *np,
+                          struct spi_eeprom *chip)
+{
+       memset(chip, 0, sizeof(*chip));
+       strncpy(chip->name, np->name, sizeof(chip->name));
+
+       if (of_find_property(np, "read-only", NULL))
+               chip->flags |= EE_READONLY;
+       return 0;
+}
+
+static int fm25_probe(struct spi_device *spi)
+{
+       struct fm25_data        *fm25 = NULL;
+       struct spi_eeprom       chip;
+       struct device_node      *np = spi->dev.of_node;
+       int                     err;
+       char                    id[FM25_ID_LEN];
+
+       /* Chip description */
+       if (!spi->dev.platform_data) {
+               if (np) {
+                       err = fm25_np_to_chip(&spi->dev, np, &chip);
+                       if (err)
+                               return err;
+               } else {
+                       dev_err(&spi->dev, "Error: no chip description\n");
+                       return -ENODEV;
+               }
+       } else
+               chip = *(struct spi_eeprom *)spi->dev.platform_data;
+
+       fm25 = devm_kzalloc(&spi->dev, sizeof(struct fm25_data), GFP_KERNEL);
+       if (!fm25)
+               return -ENOMEM;
+
+       mutex_init(&fm25->lock);
+       fm25->chip = chip;
+       fm25->spi = spi_dev_get(spi);
+       spi_set_drvdata(spi, fm25);
+
+       /* Get ID of chip */
+       fm25_id_read(fm25, id);
+       if (id[6] != 0xc2) {
+               dev_err(&spi->dev, "Error: no Cypress FRAM (id %02x)\n", id[6]);
+               return -ENODEV;
+       }
+       /* set size found in ID */
+       switch (id[7]) {
+               case 0x21:
+                       fm25->chip.byte_len = 16 * 1024;
+                       break;
+               case 0x22:
+                       fm25->chip.byte_len = 32 * 1024;
+                       break;
+               case 0x23:
+                       fm25->chip.byte_len = 64 * 1024;
+                       break;
+               case 0x24:
+                       fm25->chip.byte_len = 128 * 1024;
+                       break;
+               case 0x25:
+                       fm25->chip.byte_len = 256 * 1024;
+                       break;
+               default:
+                       dev_err(&spi->dev,
+                               "Error: unsupported size (id %02x)\n", id[7]);
+                       return -ENODEV;
+                       break;
+       }
+
+       if (fm25->chip.byte_len > 64 * 1024) {
+               fm25->addrlen = 3;
+               fm25->chip.flags |= EE_ADDR3;
+       }
+       else {
+               fm25->addrlen = 2;
+               fm25->chip.flags |= EE_ADDR2;
+       }
+
+       if (id[8])
+               fm25->has_sernum = 1;
+       else
+               fm25->has_sernum = 0;
+
+       /* Export the EEPROM bytes through sysfs, since that's convenient.
+        * And maybe to other kernel code; it might hold a board's Ethernet
+        * address, or board-specific calibration data generated on the
+        * manufacturing floor.
+        *
+        * Default to root-only access to the data; EEPROMs often hold data
+        * that's sensitive for read and/or write, like ethernet addresses,
+        * security codes, board-specific manufacturing calibrations, etc.
+        */
+       sysfs_bin_attr_init(&fm25->bin);
+       fm25->bin.attr.name = "fram";
+       fm25->bin.attr.mode = S_IRUGO;
+       fm25->bin.read = fm25_bin_read;
+       fm25->mem.read = fm25_mem_read;
+
+       fm25->bin.size = fm25->chip.byte_len;
+       if (!(chip.flags & EE_READONLY)) {
+               fm25->bin.write = fm25_bin_write;
+               fm25->bin.attr.mode |= S_IWUSR | S_IWGRP;
+               fm25->mem.write = fm25_mem_write;
+       }
+
+       err = sysfs_create_bin_file(&spi->dev.kobj, &fm25->bin);
+       if (err)
+               return err;
+
+       /* Export the FM25 serial number */
+       if (fm25->has_sernum) {
+               err = device_create_file(&spi->dev, &dev_attr_sernum);
+               if (err)
+                       return err;
+       }
+
+       if (chip.setup)
+               chip.setup(&fm25->mem, chip.context);
+
+       dev_info(&spi->dev, "%Zd %s %s fram%s\n",
+               (fm25->bin.size < 1024)
+                       ? fm25->bin.size
+                       : (fm25->bin.size / 1024),
+               (fm25->bin.size < 1024) ? "Byte" : "KByte",
+               fm25->chip.name,
+               (chip.flags & EE_READONLY) ? " (readonly)" : "");
+       return 0;
+}
+
+static int fm25_remove(struct spi_device *spi)
+{
+       struct fm25_data        *fm25;
+
+       fm25 = spi_get_drvdata(spi);
+       sysfs_remove_bin_file(&spi->dev.kobj, &fm25->bin);
+       if (fm25->has_sernum)
+               device_remove_file(&spi->dev, &dev_attr_sernum);
+       return 0;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static const struct of_device_id fm25_of_match[] = {
+       { .compatible = "cypress,fm25", },
+       { }
+};
+MODULE_DEVICE_TABLE(of, fm25_of_match);
+
+static struct spi_driver fm25_driver = {
+       .driver = {
+               .name           = "fm25",
+               .owner          = THIS_MODULE,
+               .of_match_table = fm25_of_match,
+       },
+       .probe          = fm25_probe,
+       .remove         = fm25_remove,
+};
+
+module_spi_driver(fm25_driver);
+
+MODULE_DESCRIPTION("Driver for Cypress SPI FRAMs");
+MODULE_AUTHOR("Jiri Prchal");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("spi:fram");
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to