@@ -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
@@ -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
new file mode 100644
@@ -0,0 +1,499 @@
+/*
+ * 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(*fm25), 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;
+
+ fm25->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
+ * 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 = 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",
+ .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");
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 <jiri.prchal@aksignal.cz> --- v2: changed upon Varka Bhadram coments drivers/misc/eeprom/Kconfig | 11 + drivers/misc/eeprom/Makefile | 1 + drivers/misc/eeprom/fm25.c | 499 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 511 insertions(+) -- 1.9.1 -- To unsubscribe from this list: send the line "unsubscribe linux-spi" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html