On Tue, Oct 14, 2014 at 01:53:55PM +0200, Jiri Prchal wrote: > 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 I'd think this is too much copied code. Can't you fold the changes into at25? If you bind to the fm25 compatible entry, then you know you are on that device and can act accordingly. And since this is an SPI driver, you should add the spi list to CC, not I2C! > 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(+) > > 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..c30c523 > --- /dev/null > +++ b/drivers/misc/eeprom/fm25.c > @@ -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 > +#include > +#include > +#include > +#include > +#include > +#include > + > +#include > +#include > +#include > + > +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"); > -- > 1.9.1 >