nvmem: prepare basics for FRAM support
authorJiri Prchal <jiri.prchal@aksignal.cz>
Fri, 11 Jun 2021 09:45:58 +0000 (11:45 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 11 Jun 2021 10:23:10 +0000 (12:23 +0200)
Added enum and string for FRAM (ferroelectric RAM) to expose it as file
named "fram".
Added documentation of sysfs file.

Signed-off-by: Jiri Prchal <jiri.prchal@aksignal.cz>
Link: https://lore.kernel.org/r/20210611094601.95131-2-jiri.prchal@aksignal.cz
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Documentation/ABI/testing/sysfs-class-spi-eeprom [new file with mode: 0644]
Documentation/devicetree/bindings/eeprom/at25.yaml
drivers/misc/eeprom/Kconfig
drivers/misc/eeprom/at25.c
drivers/nvmem/core.c
include/linux/nvmem-provider.h

diff --git a/Documentation/ABI/testing/sysfs-class-spi-eeprom b/Documentation/ABI/testing/sysfs-class-spi-eeprom
new file mode 100644 (file)
index 0000000..1ff7579
--- /dev/null
@@ -0,0 +1,19 @@
+What:          /sys/class/spi_master/spi<bus>/spi<bus>.<dev>/fram
+Date:          June 2021
+KernelVersion: 5.14
+Contact:       Jiri Prchal <jiri.prchal@aksignal.cz>
+Description:
+       Contains the FRAM binary data. Same as EEPROM, just another file
+       name to indicate that it employs ferroelectric process.
+       It performs write operations at bus speed - no write delays.
+
+What:          /sys/class/spi_master/spi<bus>/spi<bus>.<dev>/sernum
+Date:          May 2021
+KernelVersion: 5.14
+Contact:       Jiri Prchal <jiri.prchal@aksignal.cz>
+Description:
+       Contains the serial number of the Cypress FRAM (FM25VN) if it is
+       present.  It will be displayed as a 8 byte hex string, as read
+       from the device.
+
+       This is a read-only attribute.
index 6a2dc8b..fbf99e3 100644 (file)
@@ -4,14 +4,16 @@
 $id: "http://devicetree.org/schemas/eeprom/at25.yaml#"
 $schema: "http://devicetree.org/meta-schemas/core.yaml#"
 
-title: SPI EEPROMs compatible with Atmel's AT25
+title: SPI EEPROMs or FRAMs compatible with Atmel's AT25
 
 maintainers:
   - Christian Eggers <ceggers@arri.de>
 
 properties:
   $nodename:
-    pattern: "^eeprom@[0-9a-f]{1,2}$"
+    anyOf:
+      - pattern: "^eeprom@[0-9a-f]{1,2}$"
+      - pattern: "^fram@[0-9a-f]{1,2}$"
 
   # There are multiple known vendors who manufacture EEPROM chips compatible
   # with Atmel's AT25. The compatible string requires two items where the
@@ -31,6 +33,7 @@ properties:
               - microchip,25lc040
               - st,m95m02
               - st,m95256
+              - cypress,fm25
 
           - const: atmel,at25
 
@@ -47,7 +50,7 @@ properties:
     $ref: /schemas/types.yaml#/definitions/uint32
     enum: [1, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072]
     description:
-      Size of the eeprom page.
+      Size of the eeprom page. FRAMs don't have pages.
 
   size:
     $ref: /schemas/types.yaml#/definitions/uint32
@@ -100,9 +103,19 @@ required:
   - compatible
   - reg
   - spi-max-frequency
-  - pagesize
-  - size
-  - address-width
+
+allOf:
+  - if:
+      properties:
+        compatible:
+          not:
+            contains:
+              const: cypress,fm25
+    then:
+      required:
+        - pagesize
+        - size
+        - address-width
 
 additionalProperties: false
 
@@ -125,4 +138,10 @@ examples:
             size = <32768>;
             address-width = <16>;
         };
+
+        fram@1 {
+            compatible = "cypress,fm25", "atmel,at25";
+            reg = <1>;
+            spi-max-frequency = <40000000>;
+        };
     };
index 0f791bf..f0a7531 100644 (file)
@@ -32,12 +32,13 @@ 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
        select NVMEM
        select NVMEM_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.
 
index b76e490..6e26de6 100644 (file)
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 /*
  * at25.c -- support most SPI EEPROMs, such as Atmel AT25 models
+ *          and Cypress FRAMs FM25 models
  *
  * Copyright (C) 2006 David Brownell
  */
@@ -16,6 +17,9 @@
 #include <linux/spi/spi.h>
 #include <linux/spi/eeprom.h>
 #include <linux/property.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/math.h>
 
 /*
  * NOTE: this is an *EEPROM* driver.  The vagaries of product naming
@@ -27,6 +31,7 @@
  *   AT25M02, AT25128B
  */
 
+#define        FM25_SN_LEN     8               /* serial number length */
 struct at25_data {
        struct spi_device       *spi;
        struct mutex            lock;
@@ -34,6 +39,7 @@ struct at25_data {
        unsigned                addrlen;
        struct nvmem_config     nvmem_config;
        struct nvmem_device     *nvmem;
+       u8 sernum[FM25_SN_LEN];
 };
 
 #define        AT25_WREN       0x06            /* latch the write enable */
@@ -42,6 +48,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 +60,8 @@ struct at25_data {
 
 #define        AT25_INSTR_BIT3 0x08            /* Additional address bit in instr */
 
+#define        FM25_ID_LEN     9               /* ID length */
+
 #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 +69,9 @@ struct at25_data {
  */
 #define        EE_TIMEOUT      25
 
+#define        IS_EEPROM       0
+#define        IS_FRAM         1
+
 /*-------------------------------------------------------------------------*/
 
 #define        io_limit        PAGE_SIZE       /* bytes */
@@ -129,6 +143,51 @@ static int at25_ee_read(void *priv, unsigned int offset,
        return status;
 }
 
+/*
+ * read extra registers as ID or serial number
+ */
+static int fm25_aux_read(struct at25_data *at25, u8 *buf, uint8_t command,
+                        int len)
+{
+       int 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 = len;
+       spi_message_add_tail(&t[1], &m);
+
+       mutex_lock(&at25->lock);
+
+       status = spi_sync(at25->spi, &m);
+       dev_dbg(&at25->spi->dev, "read %d aux bytes --> %d\n", len, status);
+
+       mutex_unlock(&at25->lock);
+       return status;
+}
+
+static ssize_t sernum_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct at25_data *at25;
+
+       at25 = dev_get_drvdata(dev);
+       return sysfs_emit(buf, "%*ph\n", sizeof(at25->sernum), at25->sernum);
+}
+static DEVICE_ATTR_RO(sernum);
+
+static struct attribute *sernum_attrs[] = {
+       &dev_attr_sernum.attr,
+       NULL,
+};
+ATTRIBUTE_GROUPS(sernum);
+
 static int at25_ee_write(void *priv, unsigned int off, void *val, size_t count)
 {
        struct at25_data *at25 = priv;
@@ -303,34 +362,39 @@ static int at25_fw_to_chip(struct device *dev, struct spi_eeprom *chip)
        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;
        struct spi_eeprom       chip;
        int                     err;
        int                     sr;
-       int                     addrlen;
+       u8 id[FM25_ID_LEN];
+       u8 sernum[FM25_SN_LEN];
+       int i;
+       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)match->data;
 
        /* Chip description */
        if (!spi->dev.platform_data) {
-               err = at25_fw_to_chip(&spi->dev, &chip);
-               if (err)
-                       return err;
+               if (!is_fram) {
+                       err = at25_fw_to_chip(&spi->dev, &chip);
+                       if (err)
+                               return err;
+               }
        } else
                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;
-       }
-
        /* Ping the chip ... the status register is pretty portable,
         * unlike probing manufacturer IDs.  We do expect that system
         * firmware didn't write it in the past few milliseconds!
@@ -349,9 +413,51 @@ static int at25_probe(struct spi_device *spi)
        at25->chip = chip;
        at25->spi = spi;
        spi_set_drvdata(spi, at25);
-       at25->addrlen = addrlen;
 
-       at25->nvmem_config.type = NVMEM_TYPE_EEPROM;
+       if (is_fram) {
+               /* Get ID of chip */
+               fm25_aux_read(at25, id, FM25_RDID, FM25_ID_LEN);
+               if (id[6] != 0xc2) {
+                       dev_err(&spi->dev,
+                               "Error: no Cypress FRAM (id %02x)\n", id[6]);
+                       return -ENODEV;
+               }
+               /* set size found in ID */
+               if (id[7] < 0x21 || id[7] > 0x26) {
+                       dev_err(&spi->dev, "Error: unsupported size (id %02x)\n", id[7]);
+                       return -ENODEV;
+               }
+               chip.byte_len = int_pow(2, id[7] - 0x21 + 4) * 1024;
+
+               if (at25->chip.byte_len > 64 * 1024)
+                       at25->chip.flags |= EE_ADDR3;
+               else
+                       at25->chip.flags |= EE_ADDR2;
+
+               if (id[8]) {
+                       fm25_aux_read(at25, sernum, FM25_RDSN, FM25_SN_LEN);
+                       /* swap byte order */
+                       for (i = 0; i < FM25_SN_LEN; i++)
+                               at25->sernum[i] = sernum[FM25_SN_LEN - 1 - i];
+               }
+
+               at25->chip.page_size = PAGE_SIZE;
+               strncpy(at25->chip.name, "fm25", sizeof(at25->chip.name));
+       }
+
+       /* For now we only support 8/16/24 bit addressing */
+       if (at25->chip.flags & EE_ADDR1)
+               at25->addrlen = 1;
+       else if (at25->chip.flags & EE_ADDR2)
+               at25->addrlen = 2;
+       else if (at25->chip.flags & EE_ADDR3)
+               at25->addrlen = 3;
+       else {
+               dev_dbg(&spi->dev, "unsupported address type\n");
+               return -EINVAL;
+       }
+
+       at25->nvmem_config.type = is_fram ? NVMEM_TYPE_FRAM : NVMEM_TYPE_EEPROM;
        at25->nvmem_config.name = dev_name(&spi->dev);
        at25->nvmem_config.dev = &spi->dev;
        at25->nvmem_config.read_only = chip.flags & EE_READONLY;
@@ -370,27 +476,22 @@ static int at25_probe(struct spi_device *spi)
        if (IS_ERR(at25->nvmem))
                return PTR_ERR(at25->nvmem);
 
-       dev_info(&spi->dev, "%d %s %s eeprom%s, pagesize %u\n",
-               (chip.byte_len < 1024) ? chip.byte_len : (chip.byte_len / 1024),
-               (chip.byte_len < 1024) ? "Byte" : "KByte",
-               at25->chip.name,
-               (chip.flags & EE_READONLY) ? " (readonly)" : "",
-               at25->chip.page_size);
+       dev_info(&spi->dev, "%d %s %s %s%s, pagesize %u\n",
+                (chip.byte_len < 1024) ? chip.byte_len : (chip.byte_len / 1024),
+                (chip.byte_len < 1024) ? "Byte" : "KByte",
+                at25->chip.name, is_fram ? "fram" : "eeprom",
+                (chip.flags & EE_READONLY) ? " (readonly)" : "",
+                at25->chip.page_size);
        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",
                .of_match_table = at25_of_match,
+               .dev_groups     = sernum_groups,
        },
        .probe          = at25_probe,
 };
index b3c28a2..4d1c4f8 100644 (file)
@@ -180,6 +180,7 @@ static const char * const nvmem_type_str[] = {
        [NVMEM_TYPE_EEPROM] = "EEPROM",
        [NVMEM_TYPE_OTP] = "OTP",
        [NVMEM_TYPE_BATTERY_BACKED] = "Battery backed",
+       [NVMEM_TYPE_FRAM] = "FRAM",
 };
 
 #ifdef CONFIG_DEBUG_LOCK_ALLOC
@@ -359,6 +360,9 @@ static int nvmem_sysfs_setup_compat(struct nvmem_device *nvmem,
        if (!config->base_dev)
                return -EINVAL;
 
+       if (config->type == NVMEM_TYPE_FRAM)
+               bin_attr_nvmem_eeprom_compat.attr.name = "fram";
+
        nvmem->eeprom = bin_attr_nvmem_eeprom_compat;
        nvmem->eeprom.attr.mode = nvmem_bin_attr_get_umode(nvmem);
        nvmem->eeprom.size = nvmem->size;
index e162b75..8900035 100644 (file)
@@ -25,6 +25,7 @@ enum nvmem_type {
        NVMEM_TYPE_EEPROM,
        NVMEM_TYPE_OTP,
        NVMEM_TYPE_BATTERY_BACKED,
+       NVMEM_TYPE_FRAM,
 };
 
 #define NVMEM_DEVID_NONE       (-1)