Commit fd307a4a authored by Jiri Prchal's avatar Jiri Prchal Committed by Greg Kroah-Hartman

nvmem: prepare basics for FRAM support

Added enum and string for FRAM (ferroelectric RAM) to expose it as file
named "fram".
Added documentation of sysfs file.
Signed-off-by: default avatarJiri Prchal <jiri.prchal@aksignal.cz>
Link: https://lore.kernel.org/r/20210611094601.95131-2-jiri.prchal@aksignal.czSigned-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 989f77e3
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.
...@@ -4,14 +4,16 @@ ...@@ -4,14 +4,16 @@
$id: "http://devicetree.org/schemas/eeprom/at25.yaml#" $id: "http://devicetree.org/schemas/eeprom/at25.yaml#"
$schema: "http://devicetree.org/meta-schemas/core.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: maintainers:
- Christian Eggers <ceggers@arri.de> - Christian Eggers <ceggers@arri.de>
properties: properties:
$nodename: $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 # There are multiple known vendors who manufacture EEPROM chips compatible
# with Atmel's AT25. The compatible string requires two items where the # with Atmel's AT25. The compatible string requires two items where the
...@@ -31,6 +33,7 @@ properties: ...@@ -31,6 +33,7 @@ properties:
- microchip,25lc040 - microchip,25lc040
- st,m95m02 - st,m95m02
- st,m95256 - st,m95256
- cypress,fm25
- const: atmel,at25 - const: atmel,at25
...@@ -47,7 +50,7 @@ properties: ...@@ -47,7 +50,7 @@ properties:
$ref: /schemas/types.yaml#/definitions/uint32 $ref: /schemas/types.yaml#/definitions/uint32
enum: [1, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072] enum: [1, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072]
description: description:
Size of the eeprom page. Size of the eeprom page. FRAMs don't have pages.
size: size:
$ref: /schemas/types.yaml#/definitions/uint32 $ref: /schemas/types.yaml#/definitions/uint32
...@@ -100,9 +103,19 @@ required: ...@@ -100,9 +103,19 @@ required:
- compatible - compatible
- reg - reg
- spi-max-frequency - spi-max-frequency
- pagesize
- size allOf:
- address-width - if:
properties:
compatible:
not:
contains:
const: cypress,fm25
then:
required:
- pagesize
- size
- address-width
additionalProperties: false additionalProperties: false
...@@ -125,4 +138,10 @@ examples: ...@@ -125,4 +138,10 @@ examples:
size = <32768>; size = <32768>;
address-width = <16>; address-width = <16>;
}; };
fram@1 {
compatible = "cypress,fm25", "atmel,at25";
reg = <1>;
spi-max-frequency = <40000000>;
};
}; };
...@@ -32,12 +32,13 @@ config EEPROM_AT24 ...@@ -32,12 +32,13 @@ config EEPROM_AT24
will be called at24. will be called at24.
config EEPROM_AT25 config EEPROM_AT25
tristate "SPI EEPROMs from most vendors" tristate "SPI EEPROMs (FRAMs) from most vendors"
depends on SPI && SYSFS depends on SPI && SYSFS
select NVMEM select NVMEM
select NVMEM_SYSFS select NVMEM_SYSFS
help 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 after you configure the board init code to know about each eeprom
on your target board. on your target board.
......
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
/* /*
* at25.c -- support most SPI EEPROMs, such as Atmel AT25 models * at25.c -- support most SPI EEPROMs, such as Atmel AT25 models
* and Cypress FRAMs FM25 models
* *
* Copyright (C) 2006 David Brownell * Copyright (C) 2006 David Brownell
*/ */
...@@ -16,6 +17,9 @@ ...@@ -16,6 +17,9 @@
#include <linux/spi/spi.h> #include <linux/spi/spi.h>
#include <linux/spi/eeprom.h> #include <linux/spi/eeprom.h>
#include <linux/property.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 * NOTE: this is an *EEPROM* driver. The vagaries of product naming
...@@ -27,6 +31,7 @@ ...@@ -27,6 +31,7 @@
* AT25M02, AT25128B * AT25M02, AT25128B
*/ */
#define FM25_SN_LEN 8 /* serial number length */
struct at25_data { struct at25_data {
struct spi_device *spi; struct spi_device *spi;
struct mutex lock; struct mutex lock;
...@@ -34,6 +39,7 @@ struct at25_data { ...@@ -34,6 +39,7 @@ struct at25_data {
unsigned addrlen; unsigned addrlen;
struct nvmem_config nvmem_config; struct nvmem_config nvmem_config;
struct nvmem_device *nvmem; struct nvmem_device *nvmem;
u8 sernum[FM25_SN_LEN];
}; };
#define AT25_WREN 0x06 /* latch the write enable */ #define AT25_WREN 0x06 /* latch the write enable */
...@@ -42,6 +48,9 @@ struct at25_data { ...@@ -42,6 +48,9 @@ struct at25_data {
#define AT25_WRSR 0x01 /* write status register */ #define AT25_WRSR 0x01 /* write status register */
#define AT25_READ 0x03 /* read byte(s) */ #define AT25_READ 0x03 /* read byte(s) */
#define AT25_WRITE 0x02 /* write byte(s)/sector */ #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_nRDY 0x01 /* nRDY = write-in-progress */
#define AT25_SR_WEN 0x02 /* write enable (latched) */ #define AT25_SR_WEN 0x02 /* write enable (latched) */
...@@ -51,6 +60,8 @@ struct at25_data { ...@@ -51,6 +60,8 @@ struct at25_data {
#define AT25_INSTR_BIT3 0x08 /* Additional address bit in instr */ #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 */ #define EE_MAXADDRLEN 3 /* 24 bit addresses, up to 2 MBytes */
/* Specs often allow 5 msec for a page write, sometimes 20 msec; /* Specs often allow 5 msec for a page write, sometimes 20 msec;
...@@ -58,6 +69,9 @@ struct at25_data { ...@@ -58,6 +69,9 @@ struct at25_data {
*/ */
#define EE_TIMEOUT 25 #define EE_TIMEOUT 25
#define IS_EEPROM 0
#define IS_FRAM 1
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
#define io_limit PAGE_SIZE /* bytes */ #define io_limit PAGE_SIZE /* bytes */
...@@ -129,6 +143,51 @@ static int at25_ee_read(void *priv, unsigned int offset, ...@@ -129,6 +143,51 @@ static int at25_ee_read(void *priv, unsigned int offset,
return status; 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) static int at25_ee_write(void *priv, unsigned int off, void *val, size_t count)
{ {
struct at25_data *at25 = priv; struct at25_data *at25 = priv;
...@@ -303,34 +362,39 @@ static int at25_fw_to_chip(struct device *dev, struct spi_eeprom *chip) ...@@ -303,34 +362,39 @@ static int at25_fw_to_chip(struct device *dev, struct spi_eeprom *chip)
return 0; 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) static int at25_probe(struct spi_device *spi)
{ {
struct at25_data *at25 = NULL; struct at25_data *at25 = NULL;
struct spi_eeprom chip; struct spi_eeprom chip;
int err; int err;
int sr; 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 */ /* Chip description */
if (!spi->dev.platform_data) { if (!spi->dev.platform_data) {
err = at25_fw_to_chip(&spi->dev, &chip); if (!is_fram) {
if (err) err = at25_fw_to_chip(&spi->dev, &chip);
return err; if (err)
return err;
}
} else } else
chip = *(struct spi_eeprom *)spi->dev.platform_data; 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, /* Ping the chip ... the status register is pretty portable,
* unlike probing manufacturer IDs. We do expect that system * unlike probing manufacturer IDs. We do expect that system
* firmware didn't write it in the past few milliseconds! * firmware didn't write it in the past few milliseconds!
...@@ -349,9 +413,51 @@ static int at25_probe(struct spi_device *spi) ...@@ -349,9 +413,51 @@ static int at25_probe(struct spi_device *spi)
at25->chip = chip; at25->chip = chip;
at25->spi = spi; at25->spi = spi;
spi_set_drvdata(spi, at25); 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.name = dev_name(&spi->dev);
at25->nvmem_config.dev = &spi->dev; at25->nvmem_config.dev = &spi->dev;
at25->nvmem_config.read_only = chip.flags & EE_READONLY; at25->nvmem_config.read_only = chip.flags & EE_READONLY;
...@@ -370,27 +476,22 @@ static int at25_probe(struct spi_device *spi) ...@@ -370,27 +476,22 @@ static int at25_probe(struct spi_device *spi)
if (IS_ERR(at25->nvmem)) if (IS_ERR(at25->nvmem))
return PTR_ERR(at25->nvmem); return PTR_ERR(at25->nvmem);
dev_info(&spi->dev, "%d %s %s eeprom%s, pagesize %u\n", 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) ? chip.byte_len : (chip.byte_len / 1024),
(chip.byte_len < 1024) ? "Byte" : "KByte", (chip.byte_len < 1024) ? "Byte" : "KByte",
at25->chip.name, at25->chip.name, is_fram ? "fram" : "eeprom",
(chip.flags & EE_READONLY) ? " (readonly)" : "", (chip.flags & EE_READONLY) ? " (readonly)" : "",
at25->chip.page_size); at25->chip.page_size);
return 0; 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 = { static struct spi_driver at25_driver = {
.driver = { .driver = {
.name = "at25", .name = "at25",
.of_match_table = at25_of_match, .of_match_table = at25_of_match,
.dev_groups = sernum_groups,
}, },
.probe = at25_probe, .probe = at25_probe,
}; };
......
...@@ -180,6 +180,7 @@ static const char * const nvmem_type_str[] = { ...@@ -180,6 +180,7 @@ static const char * const nvmem_type_str[] = {
[NVMEM_TYPE_EEPROM] = "EEPROM", [NVMEM_TYPE_EEPROM] = "EEPROM",
[NVMEM_TYPE_OTP] = "OTP", [NVMEM_TYPE_OTP] = "OTP",
[NVMEM_TYPE_BATTERY_BACKED] = "Battery backed", [NVMEM_TYPE_BATTERY_BACKED] = "Battery backed",
[NVMEM_TYPE_FRAM] = "FRAM",
}; };
#ifdef CONFIG_DEBUG_LOCK_ALLOC #ifdef CONFIG_DEBUG_LOCK_ALLOC
...@@ -359,6 +360,9 @@ static int nvmem_sysfs_setup_compat(struct nvmem_device *nvmem, ...@@ -359,6 +360,9 @@ static int nvmem_sysfs_setup_compat(struct nvmem_device *nvmem,
if (!config->base_dev) if (!config->base_dev)
return -EINVAL; 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 = bin_attr_nvmem_eeprom_compat;
nvmem->eeprom.attr.mode = nvmem_bin_attr_get_umode(nvmem); nvmem->eeprom.attr.mode = nvmem_bin_attr_get_umode(nvmem);
nvmem->eeprom.size = nvmem->size; nvmem->eeprom.size = nvmem->size;
......
...@@ -25,6 +25,7 @@ enum nvmem_type { ...@@ -25,6 +25,7 @@ enum nvmem_type {
NVMEM_TYPE_EEPROM, NVMEM_TYPE_EEPROM,
NVMEM_TYPE_OTP, NVMEM_TYPE_OTP,
NVMEM_TYPE_BATTERY_BACKED, NVMEM_TYPE_BATTERY_BACKED,
NVMEM_TYPE_FRAM,
}; };
#define NVMEM_DEVID_NONE (-1) #define NVMEM_DEVID_NONE (-1)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment