This patch adds functionality for Cypress FRAMs on SPI bus, such as FM25V05,
FM25V10 etc.
Added to at25 driver:
- reading device ID and choose size and addr len from it
- serial number reading and exporting it to sysfs
- new compatible string

Signed-off-by: Jiri Prchal <jiri.prc...@aksignal.cz>
---
Moved changes back to at25.c as Wolfram Sang coment "too much copied code".

 drivers/misc/eeprom/Kconfig |   5 +-
 drivers/misc/eeprom/at25.c  | 205 ++++++++++++++++++++++++++++++++++++++++----
 2 files changed, 189 insertions(+), 21 deletions(-)

diff --git a/drivers/misc/eeprom/Kconfig b/drivers/misc/eeprom/Kconfig
index 9536852f..130a538 100644
--- a/drivers/misc/eeprom/Kconfig
+++ b/drivers/misc/eeprom/Kconfig
@@ -28,10 +28,11 @@ config EEPROM_AT24
          will be called at24.

 config EEPROM_AT25
-       tristate "SPI EEPROMs from most vendors"
+       tristate "SPI EEPROMs (FRAMs) from most vendors"
        depends on SPI && SYSFS
        help
-         Enable this driver to get read/write support to most SPI EEPROMs,
+         Enable this driver to get read/write support to most SPI EEPROMs
+         and Cypress FRAMs,
          after you configure the board init code to know about each eeprom
          on your target board.

diff --git a/drivers/misc/eeprom/at25.c b/drivers/misc/eeprom/at25.c
index 634f729..7bb9a6d 100644
--- a/drivers/misc/eeprom/at25.c
+++ b/drivers/misc/eeprom/at25.c
@@ -1,7 +1,9 @@
 /*
  * at25.c -- support most SPI EEPROMs, such as Atmel AT25 models
+ *          and Cypress FRAMs FM25 models
  *
  * Copyright (C) 2006 David Brownell
+ *              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
@@ -19,6 +21,7 @@
 #include <linux/spi/spi.h>
 #include <linux/spi/eeprom.h>
 #include <linux/of.h>
+#include <linux/of_device.h>

 /*
  * NOTE: this is an *EEPROM* driver.  The vagaries of product naming
@@ -34,6 +37,7 @@ struct at25_data {
        struct spi_eeprom       chip;
        struct bin_attribute    bin;
        unsigned                addrlen;
+       int                     has_sernum;
 };

 #define        AT25_WREN       0x06            /* latch the write enable */
@@ -42,6 +46,9 @@ struct at25_data {
 #define        AT25_WRSR       0x01            /* write status register */
 #define        AT25_READ       0x03            /* read byte(s) */
 #define        AT25_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        AT25_SR_nRDY    0x01            /* nRDY = write-in-progress */
 #define        AT25_SR_WEN     0x02            /* write enable (latched) */
@@ -51,6 +58,9 @@ struct at25_data {

 #define        AT25_INSTR_BIT3 0x08            /* Additional address bit in 
instr */

+#define        FM25_ID_LEN     9               /* ID lenght */
+#define        FM25_SN_LEN     8               /* serial number lenght */
+
 #define EE_MAXADDRLEN  3               /* 24 bit addresses, up to 2 MBytes */

 /* Specs often allow 5 msec for a page write, sometimes 20 msec;
@@ -58,6 +68,9 @@ struct at25_data {
  */
 #define        EE_TIMEOUT      25

+#define        IS_EEPROM       0
+#define        IS_FRAM         1
+
 /*-------------------------------------------------------------------------*/

 #define        io_limit        PAGE_SIZE       /* bytes */
@@ -132,6 +145,83 @@ at25_ee_read(
 }

 static ssize_t
+fm25_id_read(struct at25_data *at25, 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(&at25->lock);
+
+       status = spi_sync(at25->spi, &m);
+       dev_dbg(&at25->spi->dev,
+               "read %Zd bytes of ID --> %d\n",
+        FM25_ID_LEN, (int) status);
+
+       mutex_unlock(&at25->lock);
+       return status ? status : FM25_ID_LEN;
+}
+
+static ssize_t
+fm25_sernum_read(struct at25_data *at25, 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(&at25->lock);
+
+       status = spi_sync(at25->spi, &m);
+       dev_dbg(&at25->spi->dev,
+               "read %Zd bytes of serial number --> %d\n",
+               FM25_SN_LEN, (int) status);
+
+       mutex_unlock(&at25->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 at25_data        *at25;
+       int                     i;
+       char                    *pbuf = buf;
+
+       at25 = dev_get_drvdata(dev);
+       fm25_sernum_read(at25, 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
 at25_bin_read(struct file *filp, struct kobject *kobj,
              struct bin_attribute *bin_attr,
              char *buf, loff_t off, size_t count)
@@ -303,13 +393,20 @@ static ssize_t at25_mem_write(struct memory_accessor 
*mem, const char *buf,

 static int at25_np_to_chip(struct device *dev,
                           struct device_node *np,
-                          struct spi_eeprom *chip)
+                          struct spi_eeprom *chip,
+                          int is_fram)
 {
        u32 val;

        memset(chip, 0, sizeof(*chip));
        strncpy(chip->name, np->name, sizeof(chip->name));

+       if (is_fram) {
+               if (of_find_property(np, "read-only", NULL))
+                       chip->flags |= EE_READONLY;
+               return 0;
+       }
+
        if (of_property_read_u32(np, "size", &val) == 0 ||
            of_property_read_u32(np, "at25,byte-len", &val) == 0) {
                chip->byte_len = val;
@@ -356,6 +453,13 @@ static int at25_np_to_chip(struct device *dev,
        return 0;
 }

+static const struct of_device_id at25_of_match[] = {
+       { .compatible = "atmel,at25", .data = (const void *)IS_EEPROM },
+       { .compatible = "cypress,fm25", .data = (const void *)IS_FRAM },
+       { }
+};
+MODULE_DEVICE_TABLE(of, at25_of_match);
+
 static int at25_probe(struct spi_device *spi)
 {
        struct at25_data        *at25 = NULL;
@@ -364,11 +468,18 @@ static int at25_probe(struct spi_device *spi)
        int                     err;
        int                     sr;
        int                     addrlen;
+       char                    id[FM25_ID_LEN];
+       const struct of_device_id *match;
+       int                     is_fram = 0;
+
+       match = of_match_device(of_match_ptr(at25_of_match), &spi->dev);
+       if (match)
+               is_fram = (int)(uintptr_t)match->data;

        /* Chip description */
        if (!spi->dev.platform_data) {
                if (np) {
-                       err = at25_np_to_chip(&spi->dev, np, &chip);
+                       err = at25_np_to_chip(&spi->dev, np, &chip, is_fram);
                        if (err)
                                return err;
                } else {
@@ -379,15 +490,17 @@ static int at25_probe(struct spi_device *spi)
                chip = *(struct spi_eeprom *)spi->dev.platform_data;

        /* For now we only support 8/16/24 bit addressing */
-       if (chip.flags & EE_ADDR1)
-               addrlen = 1;
-       else if (chip.flags & EE_ADDR2)
-               addrlen = 2;
-       else if (chip.flags & EE_ADDR3)
-               addrlen = 3;
-       else {
-               dev_dbg(&spi->dev, "unsupported address type\n");
-               return -EINVAL;
+       if (!is_fram) {
+               if (chip.flags & EE_ADDR1)
+                       addrlen = 1;
+               else if (chip.flags & EE_ADDR2)
+                       addrlen = 2;
+               else if (chip.flags & EE_ADDR3)
+                       addrlen = 3;
+               else {
+                       dev_dbg(&spi->dev, "unsupported address type\n");
+                       return -EINVAL;
+               }
        }

        /* Ping the chip ... the status register is pretty portable,
@@ -410,6 +523,56 @@ static int at25_probe(struct spi_device *spi)
        spi_set_drvdata(spi, at25);
        at25->addrlen = addrlen;

+       if (is_fram) {
+               /* Get ID of chip */
+               fm25_id_read(at25, 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:
+                               at25->chip.byte_len = 16 * 1024;
+                               break;
+                       case 0x22:
+                               at25->chip.byte_len = 32 * 1024;
+                               break;
+                       case 0x23:
+                               at25->chip.byte_len = 64 * 1024;
+                               break;
+                       case 0x24:
+                               at25->chip.byte_len = 128 * 1024;
+                               break;
+                       case 0x25:
+                               at25->chip.byte_len = 256 * 1024;
+                               break;
+                       default:
+                               dev_err(&spi->dev,
+                                       "Error: unsupported size (id %02x)\n",
+                                       id[7]);
+                               return -ENODEV;
+                               break;
+               }
+
+               if (at25->chip.byte_len > 64 * 1024) {
+                       at25->addrlen = 3;
+                       at25->chip.flags |= EE_ADDR3;
+               }
+               else {
+                       at25->addrlen = 2;
+                       at25->chip.flags |= EE_ADDR2;
+               }
+
+               if (id[8])
+                       at25->has_sernum = 1;
+               else
+                       at25->has_sernum = 0;
+
+               at25->chip.page_size = PAGE_SIZE;
+       }
+
        /* 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
@@ -420,7 +583,7 @@ static int at25_probe(struct spi_device *spi)
         * security codes, board-specific manufacturing calibrations, etc.
         */
        sysfs_bin_attr_init(&at25->bin);
-       at25->bin.attr.name = "eeprom";
+       at25->bin.attr.name = is_fram ? "fram" : "eeprom";
        at25->bin.attr.mode = S_IRUSR;
        at25->bin.read = at25_bin_read;
        at25->mem.read = at25_mem_read;
@@ -436,15 +599,23 @@ static int at25_probe(struct spi_device *spi)
        if (err)
                return err;

+       /* Export the FM25 serial number */
+       if (at25->has_sernum) {
+               err = device_create_file(&spi->dev, &dev_attr_sernum);
+               if (err)
+                       return err;
+       }
+
        if (chip.setup)
                chip.setup(&at25->mem, chip.context);

-       dev_info(&spi->dev, "%Zd %s %s eeprom%s, pagesize %u\n",
+       dev_info(&spi->dev, "%Zd %s %s %s%s, pagesize %u\n",
                (at25->bin.size < 1024)
                        ? at25->bin.size
                        : (at25->bin.size / 1024),
                (at25->bin.size < 1024) ? "Byte" : "KByte",
                at25->chip.name,
+               is_fram ? "fram" : "eeprom",
                (chip.flags & EE_READONLY) ? " (readonly)" : "",
                at25->chip.page_size);
        return 0;
@@ -456,17 +627,13 @@ static int at25_remove(struct spi_device *spi)

        at25 = spi_get_drvdata(spi);
        sysfs_remove_bin_file(&spi->dev.kobj, &at25->bin);
+       if (at25->has_sernum)
+               device_remove_file(&spi->dev, &dev_attr_sernum);
        return 0;
 }

 /*-------------------------------------------------------------------------*/

-static const struct of_device_id at25_of_match[] = {
-       { .compatible = "atmel,at25", },
-       { }
-};
-MODULE_DEVICE_TABLE(of, at25_of_match);
-
 static struct spi_driver at25_driver = {
        .driver = {
                .name           = "at25",
--
1.9.1

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

Reply via email to