From 34ff636f322c6d5e4f0a52338fa63a7dff09a495 Mon Sep 17 00:00:00 2001 From: Joel Fernandes Date: Sat, 18 Jul 2026 13:15:36 -0400 Subject: scftorture: Count single_rpc offline failures in statistics output scf_torture_stats_print() aggregates each invoker thread's counters into a local scf_statistics structure before printing, but the n_single_rpc_ofl field is missing from the aggregation loop. As a result, the "single_rpc_ofl" value printed in the statistics line is always zero, even when smp_call_function_single() invocations for the RPC test have failed due to offline CPUs and been counted by the invoker threads. Add the missing accumulation so that the printed value reflects the actual counts. Signed-off-by: Joel Fernandes Signed-off-by: Paul E. McKenney --- drivers/misc/eeprom/Kconfig | 141 +++ drivers/misc/eeprom/Makefile | 10 + drivers/misc/eeprom/at24.c | 871 ++++++++++++++++++ drivers/misc/eeprom/at25.c | 585 ++++++++++++ drivers/misc/eeprom/digsy_mtc_eeprom.c | 97 ++ drivers/misc/eeprom/ee1004.c | 351 ++++++++ drivers/misc/eeprom/eeprom_93cx6.c | 383 ++++++++ drivers/misc/eeprom/eeprom_93xx46.c | 560 ++++++++++++ drivers/misc/eeprom/idt_89hpesx.c | 1515 ++++++++++++++++++++++++++++++++ drivers/misc/eeprom/m24lr.c | 606 +++++++++++++ drivers/misc/eeprom/max6875.c | 204 +++++ 11 files changed, 5323 insertions(+) create mode 100644 drivers/misc/eeprom/Kconfig create mode 100644 drivers/misc/eeprom/Makefile create mode 100644 drivers/misc/eeprom/at24.c create mode 100644 drivers/misc/eeprom/at25.c create mode 100644 drivers/misc/eeprom/digsy_mtc_eeprom.c create mode 100644 drivers/misc/eeprom/ee1004.c create mode 100644 drivers/misc/eeprom/eeprom_93cx6.c create mode 100644 drivers/misc/eeprom/eeprom_93xx46.c create mode 100644 drivers/misc/eeprom/idt_89hpesx.c create mode 100644 drivers/misc/eeprom/m24lr.c create mode 100644 drivers/misc/eeprom/max6875.c (limited to 'drivers/misc/eeprom') diff --git a/drivers/misc/eeprom/Kconfig b/drivers/misc/eeprom/Kconfig new file mode 100644 index 000000000..4d0ce47aa --- /dev/null +++ b/drivers/misc/eeprom/Kconfig @@ -0,0 +1,141 @@ +# SPDX-License-Identifier: GPL-2.0-only +menu "EEPROM support" + +config EEPROM_AT24 + tristate "I2C EEPROMs / RAMs / ROMs from most vendors" + depends on I2C && SYSFS + select NVMEM + select NVMEM_SYSFS + select REGMAP + select REGMAP_I2C + help + Enable this driver to get read/write support to most I2C EEPROMs + and compatible devices like FRAMs, SRAMs, ROMs etc. After you + configure the driver to know about each chip on your target + board. Use these generic chip names, instead of vendor-specific + ones like at24c64, 24lc02 or fm24c04: + + 24c00, 24c01, 24c02, spd (readonly 24c02), 24c04, 24c08, + 24c16, 24c32, 24c64, 24c128, 24c256, 24c512, 24c1024, 24c2048 + + Unless you like data loss puzzles, always be sure that any chip + you configure as a 24c32 (32 kbit) or larger is NOT really a + 24c16 (16 kbit) or smaller, and vice versa. Marking the chip + as read-only won't help recover from this. Also, if your chip + has any software write-protect mechanism you may want to review the + code to make sure this driver won't turn it on by accident. + + If you use this with an SMBus adapter instead of an I2C adapter, + full functionality is not available. Only smaller devices are + supported (24c16 and below, max 4 kByte). + + This driver can also be built as a module. If so, the module + will be called at24. + +config EEPROM_AT25 + tristate "SPI EEPROMs (FRAMs) from most vendors" + depends on SPI && SYSFS + select NVMEM + select NVMEM_SYSFS + select SPI_MEM + help + 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. + + This driver can also be built as a module. If so, the module + will be called at25. + +config EEPROM_MAX6875 + tristate "Maxim MAX6874/5 power supply supervisor" + depends on I2C + help + If you say yes here you get read-only support for the user EEPROM of + the Maxim MAX6874/5 EEPROM-programmable, quad power-supply + sequencer/supervisor. + + All other features of this chip should be accessed via i2c-dev. + + This driver can also be built as a module. If so, the module + will be called max6875. + + +config EEPROM_93CX6 + tristate "EEPROM 93CX6 support" + help + This is a driver for the EEPROM chipsets 93c46 and 93c66. + The driver supports both read as well as write commands. + + If unsure, say N. + +config EEPROM_93XX46 + tristate "Microwire EEPROM 93XX46 support" + depends on SPI && SYSFS + select REGMAP + select NVMEM + select NVMEM_SYSFS + help + Driver for the microwire EEPROM chipsets 93xx46x. The driver + supports both read and write commands and also the command to + erase the whole EEPROM. + + This driver can also be built as a module. If so, the module + will be called eeprom_93xx46. + + If unsure, say N. + +config EEPROM_DIGSY_MTC_CFG + bool "DigsyMTC display configuration EEPROMs device" + depends on GPIO_MPC5200 && SPI_GPIO + help + This option enables access to display configuration EEPROMs + on digsy_mtc board. You have to additionally select Microwire + EEPROM 93XX46 driver. sysfs entries will be created for that + EEPROM allowing to read/write the configuration data or to + erase the whole EEPROM. + + If unsure, say N. + +config EEPROM_IDT_89HPESX + tristate "IDT 89HPESx PCIe-switches EEPROM / CSR support" + depends on I2C && SYSFS + help + Enable this driver to get read/write access to EEPROM / CSRs + over IDT PCIe-switch i2c-slave interface. + + This driver can also be built as a module. If so, the module + will be called idt_89hpesx. + +config EEPROM_EE1004 + tristate "SPD EEPROMs on DDR4 memory modules" + depends on I2C && SYSFS + select NVMEM + select NVMEM_SYSFS + help + Enable this driver to get read support to SPD EEPROMs following + the JEDEC EE1004 standard. These are typically found on DDR4 + SDRAM memory modules. + + This driver can also be built as a module. If so, the module + will be called ee1004. + +config EEPROM_M24LR + tristate "STMicroelectronics M24LR RFID/NFC EEPROM support" + depends on I2C && SYSFS + select REGMAP_I2C + select NVMEM + select NVMEM_SYSFS + help + This enables support for STMicroelectronics M24LR RFID/NFC EEPROM + chips. These dual-interface devices expose two I2C addresses: + one for EEPROM memory access and another for control and system + configuration (e.g. UID, password handling). + + This driver provides a sysfs interface for control functions and + integrates with the nvmem subsystem for EEPROM access. + + To compile this driver as a module, choose M here: the + module will be called m24lr. + +endmenu diff --git a/drivers/misc/eeprom/Makefile b/drivers/misc/eeprom/Makefile new file mode 100644 index 000000000..8f311fd6a --- /dev/null +++ b/drivers/misc/eeprom/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_EEPROM_AT24) += at24.o +obj-$(CONFIG_EEPROM_AT25) += at25.o +obj-$(CONFIG_EEPROM_MAX6875) += max6875.o +obj-$(CONFIG_EEPROM_93CX6) += eeprom_93cx6.o +obj-$(CONFIG_EEPROM_93XX46) += eeprom_93xx46.o +obj-$(CONFIG_EEPROM_DIGSY_MTC_CFG) += digsy_mtc_eeprom.o +obj-$(CONFIG_EEPROM_IDT_89HPESX) += idt_89hpesx.o +obj-$(CONFIG_EEPROM_EE1004) += ee1004.o +obj-$(CONFIG_EEPROM_M24LR) += m24lr.o diff --git a/drivers/misc/eeprom/at24.c b/drivers/misc/eeprom/at24.c new file mode 100644 index 000000000..772c4d9fa --- /dev/null +++ b/drivers/misc/eeprom/at24.c @@ -0,0 +1,871 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * at24.c - handle most I2C EEPROMs + * + * Copyright (C) 2005-2007 David Brownell + * Copyright (C) 2008 Wolfram Sang, Pengutronix + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Address pointer is 16 bit. */ +#define AT24_FLAG_ADDR16 BIT(7) +/* sysfs-entry will be read-only. */ +#define AT24_FLAG_READONLY BIT(6) +/* sysfs-entry will be world-readable. */ +#define AT24_FLAG_IRUGO BIT(5) +/* Take always 8 addresses (24c00). */ +#define AT24_FLAG_TAKE8ADDR BIT(4) +/* Factory-programmed serial number. */ +#define AT24_FLAG_SERIAL BIT(3) +/* Factory-programmed mac address. */ +#define AT24_FLAG_MAC BIT(2) +/* Does not auto-rollover reads to the next slave address. */ +#define AT24_FLAG_NO_RDROL BIT(1) + +/* + * I2C EEPROMs from most vendors are inexpensive and mostly interchangeable. + * Differences between different vendor product lines (like Atmel AT24C or + * MicroChip 24LC, etc) won't much matter for typical read/write access. + * There are also I2C RAM chips, likewise interchangeable. One example + * would be the PCF8570, which acts like a 24c02 EEPROM (256 bytes). + * + * However, misconfiguration can lose data. "Set 16-bit memory address" + * to a part with 8-bit addressing will overwrite data. Writing with too + * big a page size also loses data. And it's not safe to assume that the + * conventional addresses 0x50..0x57 only hold eeproms; a PCF8563 RTC + * uses 0x51, for just one example. + * + * Accordingly, explicit board-specific configuration data should be used + * in almost all cases. (One partial exception is an SMBus used to access + * "SPD" data for DRAM sticks. Those only use 24c02 EEPROMs.) + * + * So this driver uses "new style" I2C driver binding, expecting to be + * told what devices exist. That may be in arch/X/mach-Y/board-Z.c or + * similar kernel-resident tables; or, configuration data coming from + * a bootloader. + * + * Other than binding model, current differences from "eeprom" driver are + * that this one handles write access and isn't restricted to 24c02 devices. + * It also handles larger devices (32 kbit and up) with two-byte addresses, + * which won't work on pure SMBus systems. + */ + +struct at24_data { + /* + * Lock protects against activities from other Linux tasks, + * but not from changes by other I2C masters. + */ + struct mutex lock; + + unsigned int write_max; + unsigned int num_addresses; + unsigned int offset_adj; + + u32 byte_len; + u16 page_size; + u8 flags; + + struct nvmem_device *nvmem; + struct regulator *vcc_reg; + void (*read_post)(unsigned int off, char *buf, size_t count); + + /* + * Some chips tie up multiple I2C addresses; dummy devices reserve + * them for us. + */ + u8 bank_addr_shift; + struct regmap *client_regmaps[] __counted_by(num_addresses); +}; + +/* + * This parameter is to help this driver avoid blocking other drivers out + * of I2C for potentially troublesome amounts of time. With a 100 kHz I2C + * clock, one 256 byte read takes about 1/43 second which is excessive; + * but the 1/170 second it takes at 400 kHz may be quite reasonable; and + * at 1 MHz (Fm+) a 1/430 second delay could easily be invisible. + * + * This value is forced to be a power of two so that writes align on pages. + */ +static unsigned int at24_io_limit = 128; +module_param_named(io_limit, at24_io_limit, uint, 0); +MODULE_PARM_DESC(at24_io_limit, "Maximum bytes per I/O (default 128)"); + +/* + * Specs often allow 5 msec for a page write, sometimes 20 msec; + * it's important to recover from write timeouts. + */ +static unsigned int at24_write_timeout = 25; +module_param_named(write_timeout, at24_write_timeout, uint, 0); +MODULE_PARM_DESC(at24_write_timeout, "Time (in ms) to try writes (default 25)"); + +struct at24_chip_data { + u32 byte_len; + u8 flags; + u8 bank_addr_shift; + void (*read_post)(unsigned int off, char *buf, size_t count); +}; + +#define AT24_CHIP_DATA(_name, _len, _flags) \ + static const struct at24_chip_data _name = { \ + .byte_len = _len, .flags = _flags, \ + } + +#define AT24_CHIP_DATA_CB(_name, _len, _flags, _read_post) \ + static const struct at24_chip_data _name = { \ + .byte_len = _len, .flags = _flags, \ + .read_post = _read_post, \ + } + +#define AT24_CHIP_DATA_BS(_name, _len, _flags, _bank_addr_shift) \ + static const struct at24_chip_data _name = { \ + .byte_len = _len, .flags = _flags, \ + .bank_addr_shift = _bank_addr_shift \ + } + +static void at24_read_post_vaio(unsigned int off, char *buf, size_t count) +{ + int i; + + if (capable(CAP_SYS_ADMIN)) + return; + + /* + * Hide VAIO private settings to regular users: + * - BIOS passwords: bytes 0x00 to 0x0f + * - UUID: bytes 0x10 to 0x1f + * - Serial number: 0xc0 to 0xdf + */ + for (i = 0; i < count; i++) { + if ((off + i <= 0x1f) || + (off + i >= 0xc0 && off + i <= 0xdf)) + buf[i] = 0; + } +} + +/* needs 8 addresses as A0-A2 are ignored */ +AT24_CHIP_DATA(at24_data_24c00, 128 / 8, AT24_FLAG_TAKE8ADDR); +/* old variants can't be handled with this generic entry! */ +AT24_CHIP_DATA(at24_data_24c01, 1024 / 8, 0); +AT24_CHIP_DATA(at24_data_24cs01, 16, + AT24_FLAG_SERIAL | AT24_FLAG_READONLY); +AT24_CHIP_DATA(at24_data_24c02, 2048 / 8, 0); +AT24_CHIP_DATA(at24_data_24cs02, 16, + AT24_FLAG_SERIAL | AT24_FLAG_READONLY); +AT24_CHIP_DATA(at24_data_24mac402, 48 / 8, + AT24_FLAG_MAC | AT24_FLAG_READONLY); +AT24_CHIP_DATA(at24_data_24mac602, 64 / 8, + AT24_FLAG_MAC | AT24_FLAG_READONLY); +AT24_CHIP_DATA(at24_data_24aa025e48, 48 / 8, + AT24_FLAG_READONLY); +AT24_CHIP_DATA(at24_data_24aa025e64, 64 / 8, + AT24_FLAG_READONLY); +/* spd is a 24c02 in memory DIMMs */ +AT24_CHIP_DATA(at24_data_spd, 2048 / 8, + AT24_FLAG_READONLY | AT24_FLAG_IRUGO); +/* 24c02_vaio is a 24c02 on some Sony laptops */ +AT24_CHIP_DATA_CB(at24_data_24c02_vaio, 2048 / 8, + AT24_FLAG_READONLY | AT24_FLAG_IRUGO, + at24_read_post_vaio); +AT24_CHIP_DATA(at24_data_24c04, 4096 / 8, 0); +AT24_CHIP_DATA(at24_data_24cs04, 16, + AT24_FLAG_SERIAL | AT24_FLAG_READONLY); +/* 24rf08 quirk is handled at i2c-core */ +AT24_CHIP_DATA(at24_data_24c08, 8192 / 8, 0); +AT24_CHIP_DATA(at24_data_24cs08, 16, + AT24_FLAG_SERIAL | AT24_FLAG_READONLY); +AT24_CHIP_DATA(at24_data_24c16, 16384 / 8, 0); +AT24_CHIP_DATA(at24_data_24cs16, 16, + AT24_FLAG_SERIAL | AT24_FLAG_READONLY); +AT24_CHIP_DATA(at24_data_24c32, 32768 / 8, AT24_FLAG_ADDR16); +/* M24C32-D Additional Write lockable page (M24C32-D order codes) */ +AT24_CHIP_DATA(at24_data_24c32d_wlp, 32, AT24_FLAG_ADDR16); +AT24_CHIP_DATA(at24_data_24cs32, 16, + AT24_FLAG_ADDR16 | AT24_FLAG_SERIAL | AT24_FLAG_READONLY); +AT24_CHIP_DATA(at24_data_24c64, 65536 / 8, AT24_FLAG_ADDR16); +/* M24C64-D Additional Write lockable page (M24C64-D order codes) */ +AT24_CHIP_DATA(at24_data_24c64d_wlp, 32, AT24_FLAG_ADDR16); +AT24_CHIP_DATA(at24_data_24cs64, 16, + AT24_FLAG_ADDR16 | AT24_FLAG_SERIAL | AT24_FLAG_READONLY); +AT24_CHIP_DATA(at24_data_24c128, 131072 / 8, AT24_FLAG_ADDR16); +AT24_CHIP_DATA(at24_data_24c256, 262144 / 8, AT24_FLAG_ADDR16); +/* M24256E Additional Write lockable page (M24256E-F order codes) */ +AT24_CHIP_DATA(at24_data_24256e_wlp, 64, AT24_FLAG_ADDR16); +AT24_CHIP_DATA(at24_data_24c512, 524288 / 8, AT24_FLAG_ADDR16); +AT24_CHIP_DATA(at24_data_24c1024, 1048576 / 8, AT24_FLAG_ADDR16); +AT24_CHIP_DATA_BS(at24_data_24c1025, 1048576 / 8, AT24_FLAG_ADDR16, 2); +AT24_CHIP_DATA(at24_data_24c2048, 2097152 / 8, AT24_FLAG_ADDR16); +/* identical to 24c08 ? */ +AT24_CHIP_DATA(at24_data_INT3499, 8192 / 8, 0); + +static const struct i2c_device_id at24_ids[] = { + { .name = "24c00", .driver_data = (kernel_ulong_t)&at24_data_24c00 }, + { .name = "24c01", .driver_data = (kernel_ulong_t)&at24_data_24c01 }, + { .name = "24cs01", .driver_data = (kernel_ulong_t)&at24_data_24cs01 }, + { .name = "24c02", .driver_data = (kernel_ulong_t)&at24_data_24c02 }, + { .name = "24cs02", .driver_data = (kernel_ulong_t)&at24_data_24cs02 }, + { .name = "24mac402", .driver_data = (kernel_ulong_t)&at24_data_24mac402 }, + { .name = "24mac602", .driver_data = (kernel_ulong_t)&at24_data_24mac602 }, + { .name = "24aa025e48", .driver_data = (kernel_ulong_t)&at24_data_24aa025e48 }, + { .name = "24aa025e64", .driver_data = (kernel_ulong_t)&at24_data_24aa025e64 }, + { .name = "spd", .driver_data = (kernel_ulong_t)&at24_data_spd }, + { .name = "24c02-vaio", .driver_data = (kernel_ulong_t)&at24_data_24c02_vaio }, + { .name = "24c04", .driver_data = (kernel_ulong_t)&at24_data_24c04 }, + { .name = "24cs04", .driver_data = (kernel_ulong_t)&at24_data_24cs04 }, + { .name = "24c08", .driver_data = (kernel_ulong_t)&at24_data_24c08 }, + { .name = "24cs08", .driver_data = (kernel_ulong_t)&at24_data_24cs08 }, + { .name = "24c16", .driver_data = (kernel_ulong_t)&at24_data_24c16 }, + { .name = "24cs16", .driver_data = (kernel_ulong_t)&at24_data_24cs16 }, + { .name = "24c32", .driver_data = (kernel_ulong_t)&at24_data_24c32 }, + { .name = "24c32d-wl", .driver_data = (kernel_ulong_t)&at24_data_24c32d_wlp }, + { .name = "24cs32", .driver_data = (kernel_ulong_t)&at24_data_24cs32 }, + { .name = "24c64", .driver_data = (kernel_ulong_t)&at24_data_24c64 }, + { .name = "24c64-wl", .driver_data = (kernel_ulong_t)&at24_data_24c64d_wlp }, + { .name = "24cs64", .driver_data = (kernel_ulong_t)&at24_data_24cs64 }, + { .name = "24c128", .driver_data = (kernel_ulong_t)&at24_data_24c128 }, + { .name = "24c256", .driver_data = (kernel_ulong_t)&at24_data_24c256 }, + { .name = "24256e-wl", .driver_data = (kernel_ulong_t)&at24_data_24256e_wlp }, + { .name = "24c512", .driver_data = (kernel_ulong_t)&at24_data_24c512 }, + { .name = "24c1024", .driver_data = (kernel_ulong_t)&at24_data_24c1024 }, + { .name = "24c1025", .driver_data = (kernel_ulong_t)&at24_data_24c1025 }, + { .name = "24c2048", .driver_data = (kernel_ulong_t)&at24_data_24c2048 }, + { .name = "at24", .driver_data = 0 }, + { /* END OF LIST */ } +}; +MODULE_DEVICE_TABLE(i2c, at24_ids); + +static const struct of_device_id at24_of_match[] = { + { .compatible = "atmel,24c00", .data = &at24_data_24c00 }, + { .compatible = "atmel,24c01", .data = &at24_data_24c01 }, + { .compatible = "atmel,24cs01", .data = &at24_data_24cs01 }, + { .compatible = "atmel,24c02", .data = &at24_data_24c02 }, + { .compatible = "atmel,24cs02", .data = &at24_data_24cs02 }, + { .compatible = "atmel,24mac402", .data = &at24_data_24mac402 }, + { .compatible = "atmel,24mac602", .data = &at24_data_24mac602 }, + { .compatible = "atmel,spd", .data = &at24_data_spd }, + { .compatible = "atmel,24c04", .data = &at24_data_24c04 }, + { .compatible = "atmel,24cs04", .data = &at24_data_24cs04 }, + { .compatible = "atmel,24c08", .data = &at24_data_24c08 }, + { .compatible = "atmel,24cs08", .data = &at24_data_24cs08 }, + { .compatible = "atmel,24c16", .data = &at24_data_24c16 }, + { .compatible = "atmel,24cs16", .data = &at24_data_24cs16 }, + { .compatible = "atmel,24c32", .data = &at24_data_24c32 }, + { .compatible = "atmel,24c32d-wl", .data = &at24_data_24c32d_wlp }, + { .compatible = "atmel,24cs32", .data = &at24_data_24cs32 }, + { .compatible = "atmel,24c64", .data = &at24_data_24c64 }, + { .compatible = "atmel,24c64d-wl", .data = &at24_data_24c64d_wlp }, + { .compatible = "atmel,24cs64", .data = &at24_data_24cs64 }, + { .compatible = "atmel,24c128", .data = &at24_data_24c128 }, + { .compatible = "atmel,24c256", .data = &at24_data_24c256 }, + { .compatible = "atmel,24c512", .data = &at24_data_24c512 }, + { .compatible = "atmel,24c1024", .data = &at24_data_24c1024 }, + { .compatible = "atmel,24c1025", .data = &at24_data_24c1025 }, + { .compatible = "atmel,24c2048", .data = &at24_data_24c2048 }, + { .compatible = "microchip,24aa025e48", .data = &at24_data_24aa025e48 }, + { .compatible = "microchip,24aa025e64", .data = &at24_data_24aa025e64 }, + { .compatible = "st,24256e-wl", .data = &at24_data_24256e_wlp }, + { /* END OF LIST */ }, +}; +MODULE_DEVICE_TABLE(of, at24_of_match); + +static const struct acpi_device_id at24_acpi_ids[] = { + { "INT3499", (kernel_ulong_t)&at24_data_INT3499 }, + { "TPF0001", (kernel_ulong_t)&at24_data_24c1024 }, + { /* END OF LIST */ } +}; +MODULE_DEVICE_TABLE(acpi, at24_acpi_ids); + +/* + * This routine supports chips which consume multiple I2C addresses. It + * computes the addressing information to be used for a given r/w request. + * Assumes that sanity checks for offset happened at sysfs-layer. + * + * Slave address and byte offset derive from the offset. Always + * set the byte address; on a multi-master board, another master + * may have changed the chip's "current" address pointer. + */ +static struct regmap *at24_translate_offset(struct at24_data *at24, + unsigned int *offset) +{ + unsigned int i; + + if (at24->flags & AT24_FLAG_ADDR16) { + i = *offset >> 16; + *offset &= 0xffff; + } else { + i = *offset >> 8; + *offset &= 0xff; + } + + return at24->client_regmaps[i]; +} + +static struct device *at24_base_client_dev(struct at24_data *at24) +{ + return regmap_get_device(at24->client_regmaps[0]); +} + +static size_t at24_adjust_read_count(struct at24_data *at24, + unsigned int offset, size_t count) +{ + unsigned int bits; + size_t remainder; + + /* + * In case of multi-address chips that don't rollover reads to + * the next slave address: truncate the count to the slave boundary, + * so that the read never straddles slaves. + */ + if (at24->flags & AT24_FLAG_NO_RDROL) { + bits = (at24->flags & AT24_FLAG_ADDR16) ? 16 : 8; + remainder = BIT(bits) - offset; + if (count > remainder) + count = remainder; + } + + if (count > at24_io_limit) + count = at24_io_limit; + + return count; +} + +static ssize_t at24_regmap_read(struct at24_data *at24, char *buf, + unsigned int offset, size_t count) +{ + unsigned long timeout, read_time; + struct regmap *regmap; + int ret; + + regmap = at24_translate_offset(at24, &offset); + count = at24_adjust_read_count(at24, offset, count); + + /* adjust offset for mac and serial read ops */ + offset += at24->offset_adj; + + timeout = jiffies + msecs_to_jiffies(at24_write_timeout); + do { + /* + * The timestamp shall be taken before the actual operation + * to avoid a premature timeout in case of high CPU load. + */ + read_time = jiffies; + + ret = regmap_bulk_read(regmap, offset, buf, count); + dev_dbg(regmap_get_device(regmap), "read %zu@%d --> %d (%ld)\n", + count, offset, ret, jiffies); + if (!ret) + return count; + + usleep_range(1000, 1500); + } while (time_before(read_time, timeout)); + + return -ETIMEDOUT; +} + +/* + * Note that if the hardware write-protect pin is pulled high, the whole + * chip is normally write protected. But there are plenty of product + * variants here, including OTP fuses and partial chip protect. + * + * We only use page mode writes; the alternative is sloooow. These routines + * write at most one page. + */ + +static size_t at24_adjust_write_count(struct at24_data *at24, + unsigned int offset, size_t count) +{ + unsigned int next_page; + + /* write_max is at most a page */ + if (count > at24->write_max) + count = at24->write_max; + + /* Never roll over backwards, to the start of this page */ + next_page = roundup(offset + 1, at24->page_size); + if (offset + count > next_page) + count = next_page - offset; + + return count; +} + +static ssize_t at24_regmap_write(struct at24_data *at24, const char *buf, + unsigned int offset, size_t count) +{ + unsigned long timeout, write_time; + struct regmap *regmap; + int ret; + + regmap = at24_translate_offset(at24, &offset); + count = at24_adjust_write_count(at24, offset, count); + timeout = jiffies + msecs_to_jiffies(at24_write_timeout); + + do { + /* + * The timestamp shall be taken before the actual operation + * to avoid a premature timeout in case of high CPU load. + */ + write_time = jiffies; + + ret = regmap_bulk_write(regmap, offset, buf, count); + dev_dbg(regmap_get_device(regmap), "write %zu@%d --> %d (%ld)\n", + count, offset, ret, jiffies); + if (!ret) + return count; + + usleep_range(1000, 1500); + } while (time_before(write_time, timeout)); + + return -ETIMEDOUT; +} + +static int at24_read(void *priv, unsigned int off, void *val, size_t count) +{ + struct at24_data *at24; + struct device *dev; + char *buf = val; + int i, ret; + + at24 = priv; + dev = at24_base_client_dev(at24); + + if (unlikely(!count)) + return count; + + if (off + count > at24->byte_len) + return -EINVAL; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + /* + * Read data from chip, protecting against concurrent updates + * from this host, but not from other I2C masters. + */ + mutex_lock(&at24->lock); + + for (i = 0; count; i += ret, count -= ret) { + ret = at24_regmap_read(at24, buf + i, off + i, count); + if (ret < 0) { + mutex_unlock(&at24->lock); + pm_runtime_put(dev); + return ret; + } + } + + mutex_unlock(&at24->lock); + + pm_runtime_put(dev); + + if (unlikely(at24->read_post)) + at24->read_post(off, buf, i); + + return 0; +} + +static int at24_write(void *priv, unsigned int off, void *val, size_t count) +{ + struct at24_data *at24; + struct device *dev; + char *buf = val; + int ret; + + at24 = priv; + dev = at24_base_client_dev(at24); + + if (unlikely(!count)) + return -EINVAL; + + if (off + count > at24->byte_len) + return -EINVAL; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + /* + * Write data to chip, protecting against concurrent updates + * from this host, but not from other I2C masters. + */ + mutex_lock(&at24->lock); + + while (count) { + ret = at24_regmap_write(at24, buf, off, count); + if (ret < 0) { + mutex_unlock(&at24->lock); + pm_runtime_put(dev); + return ret; + } + buf += ret; + off += ret; + count -= ret; + } + + mutex_unlock(&at24->lock); + + pm_runtime_put(dev); + + return 0; +} + +static int at24_make_dummy_client(struct at24_data *at24, unsigned int index, + struct i2c_client *base_client, + struct regmap_config *regmap_config) +{ + struct i2c_client *dummy_client; + struct regmap *regmap; + + dummy_client = devm_i2c_new_dummy_device(&base_client->dev, + base_client->adapter, + base_client->addr + + (index << at24->bank_addr_shift)); + if (IS_ERR(dummy_client)) + return PTR_ERR(dummy_client); + + regmap = devm_regmap_init_i2c(dummy_client, regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + at24->client_regmaps[index] = regmap; + + return 0; +} + +static unsigned int at24_get_offset_adj(u8 flags, unsigned int byte_len) +{ + if (flags & AT24_FLAG_MAC) { + /* EUI-48 starts from 0x9a, EUI-64 from 0x98 */ + return 0xa0 - byte_len; + } else if (flags & AT24_FLAG_SERIAL && flags & AT24_FLAG_ADDR16) { + /* + * For 16 bit address pointers, the word address must contain + * a '10' sequence in bits 11 and 10 regardless of the + * intended position of the address pointer. + */ + return 0x0800; + } else if (flags & AT24_FLAG_SERIAL) { + /* + * Otherwise the word address must begin with a '10' sequence, + * regardless of the intended address. + */ + return 0x0080; + } else { + return 0; + } +} + +static void at24_probe_temp_sensor(struct i2c_client *client) +{ + struct at24_data *at24 = i2c_get_clientdata(client); + struct i2c_board_info info = { .type = "jc42" }; + int ret; + u8 val; + + /* + * Byte 2 has value 11 for DDR3, earlier versions don't + * support the thermal sensor present flag + */ + ret = at24_read(at24, 2, &val, 1); + if (ret || val != 11) + return; + + /* Byte 32, bit 7 is set if temp sensor is present */ + ret = at24_read(at24, 32, &val, 1); + if (ret || !(val & BIT(7))) + return; + + info.addr = 0x18 | (client->addr & 7); + + i2c_new_client_device(client->adapter, &info); +} + +static int at24_probe(struct i2c_client *client) +{ + struct regmap_config regmap_config = { }; + struct nvmem_config nvmem_config = { }; + u32 byte_len, page_size, flags, addrw; + const struct at24_chip_data *cdata; + struct device *dev = &client->dev; + bool i2c_fn_i2c, i2c_fn_block; + unsigned int i, num_addresses; + struct at24_data *at24; + bool full_power; + struct regmap *regmap; + bool writable; + u8 test_byte; + int err; + + i2c_fn_i2c = i2c_check_functionality(client->adapter, I2C_FUNC_I2C); + i2c_fn_block = i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WRITE_I2C_BLOCK); + + cdata = i2c_get_match_data(client); + if (!cdata) + return -ENODEV; + + err = device_property_read_u32(dev, "pagesize", &page_size); + if (err) + /* + * This is slow, but we can't know all eeproms, so we better + * play safe. Specifying custom eeprom-types via device tree + * or properties is recommended anyhow. + */ + page_size = 1; + + flags = cdata->flags; + if (device_property_present(dev, "read-only")) + flags |= AT24_FLAG_READONLY; + if (device_property_present(dev, "no-read-rollover")) + flags |= AT24_FLAG_NO_RDROL; + + err = device_property_read_u32(dev, "address-width", &addrw); + if (!err) { + switch (addrw) { + case 8: + if (flags & AT24_FLAG_ADDR16) + dev_warn(dev, + "Override address width to be 8, while default is 16\n"); + flags &= ~AT24_FLAG_ADDR16; + break; + case 16: + flags |= AT24_FLAG_ADDR16; + break; + default: + dev_warn(dev, "Bad \"address-width\" property: %u\n", + addrw); + } + } + + err = device_property_read_u32(dev, "size", &byte_len); + if (err) + byte_len = cdata->byte_len; + + if (!i2c_fn_i2c && !i2c_fn_block) + page_size = 1; + + if (!page_size) + return dev_err_probe(dev, -EINVAL, "page_size must not be 0!\n"); + + if (!is_power_of_2(page_size)) + dev_warn(dev, "page_size looks suspicious (no power of 2)!\n"); + + err = device_property_read_u32(dev, "num-addresses", &num_addresses); + if (err) { + if (flags & AT24_FLAG_TAKE8ADDR) + num_addresses = 8; + else + num_addresses = DIV_ROUND_UP(byte_len, + (flags & AT24_FLAG_ADDR16) ? 65536 : 256); + } + + if ((flags & AT24_FLAG_SERIAL) && (flags & AT24_FLAG_MAC)) + return dev_err_probe(dev, -EINVAL, + "invalid device data - cannot have both AT24_FLAG_SERIAL & AT24_FLAG_MAC."); + + regmap_config.val_bits = 8; + regmap_config.reg_bits = (flags & AT24_FLAG_ADDR16) ? 16 : 8; + regmap_config.disable_locking = true; + + regmap = devm_regmap_init_i2c(client, ®map_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + at24 = devm_kzalloc(dev, struct_size(at24, client_regmaps, num_addresses), + GFP_KERNEL); + if (!at24) + return -ENOMEM; + + mutex_init(&at24->lock); + at24->byte_len = byte_len; + at24->page_size = page_size; + at24->flags = flags; + at24->read_post = cdata->read_post; + at24->bank_addr_shift = cdata->bank_addr_shift; + at24->num_addresses = num_addresses; + at24->offset_adj = at24_get_offset_adj(flags, byte_len); + at24->client_regmaps[0] = regmap; + + at24->vcc_reg = devm_regulator_get(dev, "vcc"); + if (IS_ERR(at24->vcc_reg)) + return PTR_ERR(at24->vcc_reg); + + writable = !(flags & AT24_FLAG_READONLY); + if (writable) { + at24->write_max = min_t(unsigned int, + page_size, at24_io_limit); + if (!i2c_fn_i2c && at24->write_max > I2C_SMBUS_BLOCK_MAX) + at24->write_max = I2C_SMBUS_BLOCK_MAX; + } + + /* use dummy devices for multiple-address chips */ + for (i = 1; i < num_addresses; i++) { + err = at24_make_dummy_client(at24, i, client, ®map_config); + if (err) + return err; + } + + /* + * We initialize nvmem_config.id to NVMEM_DEVID_AUTO even if the + * label property is set as some platform can have multiple eeproms + * with same label and we can not register each of those with same + * label. Failing to register those eeproms trigger cascade failure + * on such platform. + */ + nvmem_config.id = NVMEM_DEVID_AUTO; + + if (device_property_present(dev, "label")) { + err = device_property_read_string(dev, "label", + &nvmem_config.name); + if (err) + return err; + } else { + nvmem_config.name = dev_name(dev); + } + + nvmem_config.type = NVMEM_TYPE_EEPROM; + nvmem_config.dev = dev; + nvmem_config.read_only = !writable; + nvmem_config.root_only = !(flags & AT24_FLAG_IRUGO); + nvmem_config.owner = THIS_MODULE; + nvmem_config.compat = true; + nvmem_config.base_dev = dev; + nvmem_config.reg_read = at24_read; + nvmem_config.reg_write = at24_write; + nvmem_config.priv = at24; + nvmem_config.stride = 1; + nvmem_config.word_size = 1; + nvmem_config.size = byte_len; + + i2c_set_clientdata(client, at24); + + full_power = acpi_dev_state_d0(&client->dev); + if (full_power) { + err = regulator_enable(at24->vcc_reg); + if (err) + return dev_err_probe(dev, err, "Failed to enable vcc regulator\n"); + + pm_runtime_set_active(dev); + } + pm_runtime_enable(dev); + + /* + * Perform a one-byte test read to verify that the chip is functional, + * unless powering on the device is to be avoided during probe (i.e. + * it's powered off right now). + */ + if (full_power) { + err = at24_read(at24, 0, &test_byte, 1); + if (err) { + pm_runtime_disable(dev); + if (!pm_runtime_status_suspended(dev)) + regulator_disable(at24->vcc_reg); + return -ENODEV; + } + } + + at24->nvmem = devm_nvmem_register(dev, &nvmem_config); + if (IS_ERR(at24->nvmem)) { + pm_runtime_disable(dev); + if (!pm_runtime_status_suspended(dev)) + regulator_disable(at24->vcc_reg); + return dev_err_probe(dev, PTR_ERR(at24->nvmem), + "failed to register nvmem\n"); + } + + /* If this a SPD EEPROM, probe for DDR3 thermal sensor */ + if (cdata == &at24_data_spd) + at24_probe_temp_sensor(client); + + pm_runtime_idle(dev); + + if (writable) + dev_info(dev, "%u byte %s EEPROM, writable, %u bytes/write\n", + byte_len, client->name, at24->write_max); + else + dev_info(dev, "%u byte %s EEPROM, read-only\n", + byte_len, client->name); + + return 0; +} + +static void at24_remove(struct i2c_client *client) +{ + struct at24_data *at24 = i2c_get_clientdata(client); + + pm_runtime_disable(&client->dev); + if (acpi_dev_state_d0(&client->dev)) { + if (!pm_runtime_status_suspended(&client->dev)) + regulator_disable(at24->vcc_reg); + pm_runtime_set_suspended(&client->dev); + } +} + +static int __maybe_unused at24_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct at24_data *at24 = i2c_get_clientdata(client); + + return regulator_disable(at24->vcc_reg); +} + +static int __maybe_unused at24_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct at24_data *at24 = i2c_get_clientdata(client); + + return regulator_enable(at24->vcc_reg); +} + +static const struct dev_pm_ops at24_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(at24_suspend, at24_resume, NULL) +}; + +static struct i2c_driver at24_driver = { + .driver = { + .name = "at24", + .pm = &at24_pm_ops, + .of_match_table = at24_of_match, + .acpi_match_table = at24_acpi_ids, + }, + .probe = at24_probe, + .remove = at24_remove, + .id_table = at24_ids, + .flags = I2C_DRV_ACPI_WAIVE_D0_PROBE, +}; + +static int __init at24_init(void) +{ + if (!at24_io_limit) { + pr_err("at24: at24_io_limit must not be 0!\n"); + return -EINVAL; + } + + at24_io_limit = rounddown_pow_of_two(at24_io_limit); + return i2c_add_driver(&at24_driver); +} +module_init(at24_init); + +static void __exit at24_exit(void) +{ + i2c_del_driver(&at24_driver); +} +module_exit(at24_exit); + +MODULE_DESCRIPTION("Driver for most I2C EEPROMs"); +MODULE_AUTHOR("David Brownell and Wolfram Sang"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/eeprom/at25.c b/drivers/misc/eeprom/at25.c new file mode 100644 index 000000000..bc2cfb75d --- /dev/null +++ b/drivers/misc/eeprom/at25.c @@ -0,0 +1,585 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for most of the SPI EEPROMs, such as Atmel AT25 models + * and Cypress FRAMs FM25 models. + * + * Copyright (C) 2006 David Brownell + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +/* + * NOTE: this is an *EEPROM* driver. The vagaries of product naming + * mean that some AT25 products are EEPROMs, and others are FLASH. + * Handle FLASH chips with the drivers/mtd/devices/m25p80.c driver, + * not this one! + * + * EEPROMs that can be used with this driver include, for example: + * AT25M02, AT25128B + */ + +#define FM25_SN_LEN 8 /* serial number length */ +#define FM25_MAX_ID_LEN 9 /* ID length */ +#define EE_MAXADDRLEN 3 /* 24 bit addresses, up to 2 MBytes */ + +struct at25_data { + struct spi_eeprom chip; + struct spi_mem *spimem; + struct mutex lock; + unsigned addrlen; + struct nvmem_config nvmem_config; + struct nvmem_device *nvmem; + u8 sernum[FM25_SN_LEN]; + u8 id[FM25_MAX_ID_LEN]; + u8 id_len; +}; + +#define AT25_WREN 0x06 /* latch the write enable */ +#define AT25_WRDI 0x04 /* reset the write enable */ +#define AT25_RDSR 0x05 /* read status register */ +#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) */ +#define AT25_SR_BP0 0x04 /* BP for software writeprotect */ +#define AT25_SR_BP1 0x08 +#define AT25_SR_WPEN 0x80 /* writeprotect enable */ + +#define AT25_INSTR_BIT3 0x08 /* additional address bit in instr */ + +/* + * Specs often allow 5ms for a page write, sometimes 20ms; + * it's important to recover from write timeouts. + */ +#define EE_TIMEOUT 25 + +/*-------------------------------------------------------------------------*/ + +#define io_limit PAGE_SIZE /* bytes */ + +/* Handle the address MSB as part of instruction byte */ +static u8 at25_instr(struct at25_data *at25, u8 instr, unsigned int off) +{ + if (!(at25->chip.flags & EE_INSTR_BIT3_IS_ADDR)) + return instr; + if (off < BIT(at25->addrlen * 8)) + return instr; + return instr | AT25_INSTR_BIT3; +} + +static int at25_ee_read(void *priv, unsigned int offset, + void *val, size_t count) +{ + u8 *bounce __free(kfree) = kmalloc(min(count, io_limit), GFP_KERNEL); + struct at25_data *at25 = priv; + char *buf = val; + unsigned int msg_offset = offset; + size_t bytes_left = count; + size_t segment; + int status; + + if (!bounce) + return -ENOMEM; + + if (unlikely(offset >= at25->chip.byte_len)) + return -EINVAL; + if ((offset + count) > at25->chip.byte_len) + count = at25->chip.byte_len - offset; + if (unlikely(!count)) + return -EINVAL; + + do { + struct spi_mem_op op; + + segment = min(bytes_left, io_limit); + + op = (struct spi_mem_op)SPI_MEM_OP(SPI_MEM_OP_CMD(at25_instr(at25, AT25_READ, + msg_offset), 1), + SPI_MEM_OP_ADDR(at25->addrlen, msg_offset, 1), + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_IN(segment, bounce, 1)); + + status = spi_mem_adjust_op_size(at25->spimem, &op); + if (status) + return status; + segment = op.data.nbytes; + + mutex_lock(&at25->lock); + status = spi_mem_exec_op(at25->spimem, &op); + mutex_unlock(&at25->lock); + if (status) + return status; + memcpy(buf, bounce, segment); + + msg_offset += segment; + buf += segment; + bytes_left -= segment; + } while (bytes_left > 0); + + dev_dbg(&at25->spimem->spi->dev, "read %zu bytes at %d\n", + count, offset); + return 0; +} + +/* + * Read extra registers as ID or serial number + * + * Allow for the callers to provide @buf on stack (not necessary DMA-capable) + * by allocating a bounce buffer internally. + */ +static int fm25_aux_read(struct at25_data *at25, u8 *buf, uint8_t command, + int len) +{ + u8 *bounce __free(kfree) = kmalloc(len, GFP_KERNEL); + struct spi_mem_op op; + int status; + + if (!bounce) + return -ENOMEM; + + op = (struct spi_mem_op)SPI_MEM_OP(SPI_MEM_OP_CMD(command, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_IN(len, bounce, 1)); + + status = spi_mem_exec_op(at25->spimem, &op); + dev_dbg(&at25->spimem->spi->dev, "read %d aux bytes --> %d\n", len, status); + if (status) + return status; + + memcpy(buf, bounce, len); + + return 0; +} + +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", (int)sizeof(at25->sernum), at25->sernum); +} +static DEVICE_ATTR_RO(sernum); + +static ssize_t jedec_id_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct at25_data *at25; + + at25 = dev_get_drvdata(dev); + + if (!at25->id_len) + return -EOPNOTSUPP; + + return sysfs_emit(buf, "%*phN\n", at25->id_len, at25->id); +} +static DEVICE_ATTR_RO(jedec_id); + +static struct attribute *at25_attrs[] = { + &dev_attr_sernum.attr, + &dev_attr_jedec_id.attr, + NULL, +}; +ATTRIBUTE_GROUPS(at25); + +/* + * Poll Read Status Register with timeout + * + * Return: + * 0, if the chip is ready + * [positive] Status Register value as-is, if the chip is busy + * [negative] error code in case of read failure + */ +static int at25_wait_ready(struct at25_data *at25) +{ + u8 *bounce __free(kfree) = kmalloc(1, GFP_KERNEL); + struct spi_mem_op op; + int status; + + if (!bounce) + return -ENOMEM; + + op = (struct spi_mem_op)SPI_MEM_OP(SPI_MEM_OP_CMD(AT25_RDSR, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_IN(1, bounce, 1)); + + read_poll_timeout(spi_mem_exec_op, status, + status || !(bounce[0] & AT25_SR_nRDY), false, + USEC_PER_MSEC, USEC_PER_MSEC * EE_TIMEOUT, + at25->spimem, &op); + if (status < 0) + return status; + if (!(bounce[0] & AT25_SR_nRDY)) + return 0; + + return bounce[0]; +} + +static int at25_ee_write(void *priv, unsigned int off, void *val, size_t count) +{ + u8 *bounce __free(kfree) = kmalloc(min(count, io_limit), GFP_KERNEL); + struct at25_data *at25 = priv; + const char *buf = val; + unsigned int buf_size; + int status; + + if (unlikely(off >= at25->chip.byte_len)) + return -EFBIG; + if ((off + count) > at25->chip.byte_len) + count = at25->chip.byte_len - off; + if (unlikely(!count)) + return -EINVAL; + + buf_size = at25->chip.page_size; + + 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. + */ + guard(mutex)(&at25->lock); + do { + struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(AT25_WREN, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_NO_DATA); + unsigned int segment; + + status = spi_mem_exec_op(at25->spimem, &op); + if (status < 0) { + dev_dbg(&at25->spimem->spi->dev, "WREN --> %d\n", status); + return status; + } + + /* Write as much of a page as we can */ + segment = buf_size - (off % buf_size); + if (segment > count) + segment = count; + if (segment > io_limit) + segment = io_limit; + + op = (struct spi_mem_op)SPI_MEM_OP(SPI_MEM_OP_CMD(at25_instr(at25, AT25_WRITE, off), + 1), + SPI_MEM_OP_ADDR(at25->addrlen, off, 1), + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_OUT(segment, bounce, 1)); + + status = spi_mem_adjust_op_size(at25->spimem, &op); + if (status) + return status; + segment = op.data.nbytes; + + memcpy(bounce, buf, segment); + + status = spi_mem_exec_op(at25->spimem, &op); + dev_dbg(&at25->spimem->spi->dev, "write %u bytes at %u --> %d\n", + segment, off, status); + if (status) + return status; + + /* + * REVISIT this should detect (or prevent) failed writes + * to read-only sections of the EEPROM... + */ + + status = at25_wait_ready(at25); + if (status < 0) { + dev_err_probe(&at25->spimem->spi->dev, status, + "Read Status Redister command failed\n"); + return status; + } + if (status) { + dev_dbg(&at25->spimem->spi->dev, + "Status %02x\n", status); + dev_err(&at25->spimem->spi->dev, + "write %u bytes offset %u, timeout after %u msecs\n", + segment, off, EE_TIMEOUT); + return -ETIMEDOUT; + } + + off += segment; + buf += segment; + count -= segment; + + } while (count > 0); + + return status; +} + +/*-------------------------------------------------------------------------*/ + +static int at25_fw_to_chip(struct device *dev, struct spi_eeprom *chip) +{ + u32 val; + int err; + + strscpy(chip->name, "at25", sizeof(chip->name)); + + err = device_property_read_u32(dev, "size", &val); + if (err) + err = device_property_read_u32(dev, "at25,byte-len", &val); + if (err) { + dev_err(dev, "Error: missing \"size\" property\n"); + return err; + } + chip->byte_len = val; + + err = device_property_read_u32(dev, "pagesize", &val); + if (err) + err = device_property_read_u32(dev, "at25,page-size", &val); + if (err) { + dev_err(dev, "Error: missing \"pagesize\" property\n"); + return err; + } + chip->page_size = val; + + err = device_property_read_u32(dev, "address-width", &val); + if (err) { + err = device_property_read_u32(dev, "at25,addr-mode", &val); + if (err) { + dev_err(dev, "Error: missing \"address-width\" property\n"); + return err; + } + chip->flags = (u16)val; + } else { + switch (val) { + case 9: + chip->flags |= EE_INSTR_BIT3_IS_ADDR; + fallthrough; + case 8: + chip->flags |= EE_ADDR1; + break; + case 16: + chip->flags |= EE_ADDR2; + break; + case 24: + chip->flags |= EE_ADDR3; + break; + default: + dev_err(dev, + "Error: bad \"address-width\" property: %u\n", + val); + return -ENODEV; + } + if (device_property_present(dev, "read-only")) + chip->flags |= EE_READONLY; + } + return 0; +} + +static int at25_fram_to_chip(struct device *dev, struct spi_eeprom *chip) +{ + struct at25_data *at25 = container_of(chip, struct at25_data, chip); + u8 sernum[FM25_SN_LEN]; + u8 id[FM25_MAX_ID_LEN]; + u32 val; + int i; + + strscpy(chip->name, "fm25", sizeof(chip->name)); + + if (!device_property_read_u32(dev, "size", &val)) { + chip->byte_len = val; + } else { + /* Get ID of chip */ + fm25_aux_read(at25, id, FM25_RDID, FM25_MAX_ID_LEN); + + /* Store the unprocessed ID for exposing via sysfs */ + memcpy(at25->id, id, FM25_MAX_ID_LEN); + at25->id_len = FM25_MAX_ID_LEN; + + /* There are inside-out FRAM variations, detect them and reverse the ID bytes */ + if (id[6] == 0x7f && id[2] == 0xc2) + for (i = 0; i < ARRAY_SIZE(id) / 2; i++) { + u8 tmp = id[i]; + int j = ARRAY_SIZE(id) - i - 1; + + id[i] = id[j]; + id[j] = tmp; + } + + if (id[6] == 0xc2) { + at25->id_len = 9; + switch (id[7]) { + case 0x21 ... 0x26: + chip->byte_len = BIT(id[7] - 0x21 + 4) * 1024; + break; + case 0x2a ... 0x30: + /* CY15B102QN ... CY15B116QN */ + chip->byte_len = BIT(((id[7] >> 1) & 0xf) + 13); + break; + default: + dev_err(dev, "Error: unsupported size (id %02x)\n", id[7]); + return -ENODEV; + } + } else if (id[2] == 0x82 && id[3] == 0x06) { + at25->id_len = 8; + switch (id[1]) { + case 0x51 ... 0x54: + /* CY15B102QSN ... CY15B204QSN */ + chip->byte_len = BIT(((id[0] >> 3) & 0x1F) + 9); + break; + default: + dev_err(dev, "Error: unsupported product id %02x\n", id[1]); + return -ENODEV; + } + } else { + dev_err(dev, "Error: unrecognized JEDEC ID format: %*ph\n", + FM25_MAX_ID_LEN, id); + return -ENODEV; + } + + 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]; + } + + if (chip->byte_len > 64 * 1024) + chip->flags |= EE_ADDR3; + else + chip->flags |= EE_ADDR2; + + chip->page_size = PAGE_SIZE; + return 0; +} + +static const struct of_device_id at25_of_match[] = { + { .compatible = "atmel,at25" }, + { .compatible = "cypress,fm25" }, + { } +}; +MODULE_DEVICE_TABLE(of, at25_of_match); + +static const struct spi_device_id at25_spi_ids[] = { + { .name = "at25" }, + { .name = "fm25" }, + { } +}; +MODULE_DEVICE_TABLE(spi, at25_spi_ids); + +static int at25_probe(struct spi_mem *mem) +{ + struct spi_device *spi = mem->spi; + struct spi_eeprom *pdata; + struct at25_data *at25; + bool is_fram; + int err; + + at25 = devm_kzalloc(&spi->dev, sizeof(*at25), GFP_KERNEL); + if (!at25) + return -ENOMEM; + + at25->spimem = mem; + + /* + * Ping the chip ... the status register is pretty portable, + * unlike probing manufacturer IDs. + */ + err = at25_wait_ready(at25); + if (err < 0) + return dev_err_probe(&spi->dev, err, "Read Status Register command failed\n"); + if (err) { + dev_err(&spi->dev, "Not ready (%02x)\n", err); + return -ENXIO; + } + + mutex_init(&at25->lock); + spi_set_drvdata(spi, at25); + + is_fram = fwnode_device_is_compatible(dev_fwnode(&spi->dev), "cypress,fm25"); + + /* Chip description */ + pdata = dev_get_platdata(&spi->dev); + if (pdata) { + at25->chip = *pdata; + } else { + if (is_fram) + err = at25_fram_to_chip(&spi->dev, &at25->chip); + else + err = at25_fw_to_chip(&spi->dev, &at25->chip); + if (err) + return err; + } + + /* 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 = at25->chip.flags & EE_READONLY; + at25->nvmem_config.root_only = true; + at25->nvmem_config.owner = THIS_MODULE; + at25->nvmem_config.compat = true; + at25->nvmem_config.base_dev = &spi->dev; + at25->nvmem_config.reg_read = at25_ee_read; + at25->nvmem_config.reg_write = at25_ee_write; + at25->nvmem_config.priv = at25; + at25->nvmem_config.stride = 1; + at25->nvmem_config.word_size = 1; + at25->nvmem_config.size = at25->chip.byte_len; + + at25->nvmem = devm_nvmem_register(&spi->dev, &at25->nvmem_config); + if (IS_ERR(at25->nvmem)) + return PTR_ERR(at25->nvmem); + + dev_info(&spi->dev, "%d %s %s %s%s, pagesize %u\n", + (at25->chip.byte_len < 1024) ? + at25->chip.byte_len : (at25->chip.byte_len / 1024), + (at25->chip.byte_len < 1024) ? "Byte" : "KByte", + at25->chip.name, is_fram ? "fram" : "eeprom", + (at25->chip.flags & EE_READONLY) ? " (readonly)" : "", + at25->chip.page_size); + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static struct spi_mem_driver at25_driver = { + .spidrv = { + .driver = { + .name = "at25", + .of_match_table = at25_of_match, + .dev_groups = at25_groups, + }, + .id_table = at25_spi_ids, + }, + .probe = at25_probe, +}; + +module_spi_mem_driver(at25_driver); + +MODULE_DESCRIPTION("Driver for most SPI EEPROMs"); +MODULE_AUTHOR("David Brownell"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/eeprom/digsy_mtc_eeprom.c b/drivers/misc/eeprom/digsy_mtc_eeprom.c new file mode 100644 index 000000000..ee58f7ce5 --- /dev/null +++ b/drivers/misc/eeprom/digsy_mtc_eeprom.c @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * EEPROMs access control driver for display configuration EEPROMs + * on DigsyMTC board. + * + * (C) 2011 DENX Software Engineering, Anatolij Gustschin + * + * FIXME: this driver is used on a device-tree probed platform: it + * should be defined as a bit-banged SPI device and probed from the device + * tree and not like this with static grabbing of a few numbered GPIO + * lines at random. + * + * Add proper SPI and EEPROM in arch/powerpc/boot/dts/digsy_mtc.dts + * and delete this driver. + */ + +#include +#include +#include +#include +#include +#include + +#define GPIO_EEPROM_CLK 216 +#define GPIO_EEPROM_CS 210 +#define GPIO_EEPROM_DI 217 +#define GPIO_EEPROM_DO 249 +#define GPIO_EEPROM_OE 255 +#define EE_SPI_BUS_NUM 1 + +static const struct property_entry digsy_mtc_spi_properties[] = { + PROPERTY_ENTRY_U32("data-size", 8), + { } +}; + +static const struct software_node digsy_mtc_spi_node = { + .properties = digsy_mtc_spi_properties, +}; + +static struct spi_gpio_platform_data eeprom_spi_gpio_data = { + .num_chipselect = 1, +}; + +static struct platform_device digsy_mtc_eeprom = { + .name = "spi_gpio", + .id = EE_SPI_BUS_NUM, + .dev = { + .platform_data = &eeprom_spi_gpio_data, + }, +}; + +static struct gpiod_lookup_table eeprom_spi_gpiod_table = { + .dev_id = "spi_gpio.1", + .table = { + GPIO_LOOKUP("gpio@b00", GPIO_EEPROM_CLK, + "sck", GPIO_ACTIVE_HIGH), + GPIO_LOOKUP("gpio@b00", GPIO_EEPROM_DI, + "mosi", GPIO_ACTIVE_HIGH), + GPIO_LOOKUP("gpio@b00", GPIO_EEPROM_DO, + "miso", GPIO_ACTIVE_HIGH), + GPIO_LOOKUP("gpio@b00", GPIO_EEPROM_CS, + "cs", GPIO_ACTIVE_HIGH), + GPIO_LOOKUP("gpio@b00", GPIO_EEPROM_OE, + "select", GPIO_ACTIVE_LOW), + { }, + }, +}; + +static struct spi_board_info digsy_mtc_eeprom_info[] __initdata = { + { + .modalias = "eeprom-93xx46", + .max_speed_hz = 1000000, + .bus_num = EE_SPI_BUS_NUM, + .chip_select = 0, + .mode = SPI_MODE_0, + }, +}; + +static int __init digsy_mtc_eeprom_devices_init(void) +{ + int ret; + + gpiod_add_lookup_table(&eeprom_spi_gpiod_table); + spi_register_board_info(digsy_mtc_eeprom_info, + ARRAY_SIZE(digsy_mtc_eeprom_info)); + + ret = device_add_software_node(&digsy_mtc_eeprom.dev, &digsy_mtc_spi_node); + if (ret) + return ret; + + ret = platform_device_register(&digsy_mtc_eeprom); + if (ret) + device_remove_software_node(&digsy_mtc_eeprom.dev); + + return ret; +} +device_initcall(digsy_mtc_eeprom_devices_init); diff --git a/drivers/misc/eeprom/ee1004.c b/drivers/misc/eeprom/ee1004.c new file mode 100644 index 000000000..923f404a4 --- /dev/null +++ b/drivers/misc/eeprom/ee1004.c @@ -0,0 +1,351 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ee1004 - driver for DDR4 SPD EEPROMs + * + * Copyright (C) 2017-2019 Jean Delvare + * + * Based on the at24 driver: + * Copyright (C) 2005-2007 David Brownell + * Copyright (C) 2008 Wolfram Sang, Pengutronix + */ + +#include +#include +#include +#include +#include +#include +#include + +/* + * DDR4 memory modules use special EEPROMs following the Jedec EE1004 + * specification. These are 512-byte EEPROMs using a single I2C address + * in the 0x50-0x57 range for data. One of two 256-byte page is selected + * by writing a command to I2C address 0x36 or 0x37 on the same I2C bus. + * + * Therefore we need to request these 2 additional addresses, and serialize + * access to all such EEPROMs with a single mutex. + * + * We assume it is safe to read up to 32 bytes at once from these EEPROMs. + * We use SMBus access even if I2C is available, these EEPROMs are small + * enough, and reading from them infrequent enough, that we favor simplicity + * over performance. + */ + +#define EE1004_MAX_BUSSES 8 +#define EE1004_ADDR_SET_PAGE 0x36 +#define EE1004_NUM_PAGES 2 +#define EE1004_PAGE_SIZE 256 +#define EE1004_PAGE_SHIFT 8 +#define EE1004_EEPROM_SIZE (EE1004_PAGE_SIZE * EE1004_NUM_PAGES) + +/* + * Mutex protects ee1004_set_page and ee1004_dev_count, and must be held + * from page selection to end of read. + */ +static DEFINE_MUTEX(ee1004_bus_lock); + +static struct ee1004_bus_data { + struct i2c_adapter *adap; + struct i2c_client *set_page[EE1004_NUM_PAGES]; + unsigned int dev_count; + int current_page; +} ee1004_bus_data[EE1004_MAX_BUSSES]; + +static const struct i2c_device_id ee1004_ids[] = { + { "ee1004" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ee1004_ids); + +/*-------------------------------------------------------------------------*/ + +static struct ee1004_bus_data *ee1004_get_bus_data(struct i2c_adapter *adap) +{ + int i; + + for (i = 0; i < EE1004_MAX_BUSSES; i++) + if (ee1004_bus_data[i].adap == adap) + return ee1004_bus_data + i; + + /* If not existent yet, create new entry */ + for (i = 0; i < EE1004_MAX_BUSSES; i++) + if (!ee1004_bus_data[i].adap) { + ee1004_bus_data[i].adap = adap; + return ee1004_bus_data + i; + } + + return NULL; +} + +static int ee1004_get_current_page(struct ee1004_bus_data *bd) +{ + int err; + + err = i2c_smbus_read_byte(bd->set_page[0]); + if (err == -ENXIO) { + /* Nack means page 1 is selected */ + return 1; + } + if (err < 0) { + /* Anything else is a real error, bail out */ + return err; + } + + /* Ack means page 0 is selected, returned value meaningless */ + return 0; +} + +static int ee1004_set_current_page(struct i2c_client *client, int page) +{ + struct ee1004_bus_data *bd = i2c_get_clientdata(client); + int ret; + + if (page == bd->current_page) + return 0; + + /* Data is ignored */ + ret = i2c_smbus_write_byte(bd->set_page[page], 0x00); + /* + * Don't give up just yet. Some memory modules will select the page + * but not ack the command. Check which page is selected now. + */ + if (ret == -ENXIO && ee1004_get_current_page(bd) == page) + ret = 0; + if (ret < 0) { + dev_err(&client->dev, "Failed to select page %d (%d)\n", page, ret); + return ret; + } + + dev_dbg(&client->dev, "Selected page %d\n", page); + bd->current_page = page; + + return 0; +} + +static ssize_t ee1004_eeprom_read(struct i2c_client *client, char *buf, + unsigned int offset, size_t count) +{ + int status, page; + + page = offset >> EE1004_PAGE_SHIFT; + offset &= (1 << EE1004_PAGE_SHIFT) - 1; + + status = ee1004_set_current_page(client, page); + if (status) + return status; + + /* Can't cross page boundaries */ + if (offset + count > EE1004_PAGE_SIZE) + count = EE1004_PAGE_SIZE - offset; + + if (count > I2C_SMBUS_BLOCK_MAX) + count = I2C_SMBUS_BLOCK_MAX; + + return i2c_smbus_read_i2c_block_data_or_emulated(client, offset, count, buf); +} + +static int ee1004_read(void *priv, unsigned int off, void *val, size_t count) +{ + struct i2c_client *client = priv; + char *buf = val; + int ret; + + if (unlikely(!count)) + return count; + + if (off + count > EE1004_EEPROM_SIZE) + return -EINVAL; + + /* + * Read data from chip, protecting against concurrent access to + * other EE1004 SPD EEPROMs on the same adapter. + */ + mutex_lock(&ee1004_bus_lock); + + while (count) { + ret = ee1004_eeprom_read(client, buf, off, count); + if (ret < 0) { + mutex_unlock(&ee1004_bus_lock); + return ret; + } + + buf += ret; + off += ret; + count -= ret; + } + + mutex_unlock(&ee1004_bus_lock); + + return 0; +} + +static void ee1004_probe_temp_sensor(struct i2c_client *client) +{ + struct i2c_board_info info = { .type = "jc42" }; + unsigned short addr = 0x18 | (client->addr & 7); + unsigned short addr_list[] = { addr, I2C_CLIENT_END }; + u8 data[2]; + int ret; + + /* byte 14, bit 7 is set if temp sensor is present */ + ret = ee1004_eeprom_read(client, data, 14, 1); + if (ret != 1) + return; + + if (!(data[0] & BIT(7))) { + /* + * If the SPD data suggests that there is no temperature + * sensor, it may still be there for SPD revision 1.0. + * See SPD Annex L, Revision 1 and 2, for details. + * Check DIMM type and SPD revision; if it is a DDR4 + * with SPD revision 1.0, check the thermal sensor address + * and instantiate the jc42 driver if a chip is found at + * that address. + * It is not necessary to check if there is a chip at the + * temperature sensor address since i2c_new_scanned_device() + * will do that and return silently if no chip is found. + */ + ret = ee1004_eeprom_read(client, data, 1, 2); + if (ret != 2 || data[0] != 0x10 || data[1] != 0x0c) + return; + } + i2c_new_scanned_device(client->adapter, &info, addr_list, NULL); +} + +static void ee1004_cleanup(int idx, struct ee1004_bus_data *bd) +{ + if (--bd->dev_count == 0) { + while (--idx >= 0) + i2c_unregister_device(bd->set_page[idx]); + memset(bd, 0, sizeof(struct ee1004_bus_data)); + } +} + +static void ee1004_cleanup_bus_data(void *data) +{ + struct ee1004_bus_data *bd = data; + + /* Remove page select clients if this is the last device */ + mutex_lock(&ee1004_bus_lock); + ee1004_cleanup(EE1004_NUM_PAGES, bd); + mutex_unlock(&ee1004_bus_lock); +} + +static int ee1004_init_bus_data(struct i2c_client *client) +{ + struct ee1004_bus_data *bd; + int err, cnr = 0; + + bd = ee1004_get_bus_data(client->adapter); + if (!bd) + return dev_err_probe(&client->dev, -ENOSPC, "Only %d busses supported", + EE1004_MAX_BUSSES); + + i2c_set_clientdata(client, bd); + + if (++bd->dev_count == 1) { + /* Use 2 dummy devices for page select command */ + for (cnr = 0; cnr < EE1004_NUM_PAGES; cnr++) { + struct i2c_client *cl; + + cl = i2c_new_dummy_device(client->adapter, EE1004_ADDR_SET_PAGE + cnr); + if (IS_ERR(cl)) { + err = PTR_ERR(cl); + goto err_out; + } + + bd->set_page[cnr] = cl; + } + + /* Remember current page to avoid unneeded page select */ + err = ee1004_get_current_page(bd); + if (err < 0) + goto err_out; + + dev_dbg(&client->dev, "Currently selected page: %d\n", err); + bd->current_page = err; + } + + return 0; + +err_out: + ee1004_cleanup(cnr, bd); + + return err; +} + +static int ee1004_probe(struct i2c_client *client) +{ + struct nvmem_config config = { + .dev = &client->dev, + .name = dev_name(&client->dev), + .id = NVMEM_DEVID_NONE, + .owner = THIS_MODULE, + .type = NVMEM_TYPE_EEPROM, + .read_only = true, + .root_only = false, + .reg_read = ee1004_read, + .size = EE1004_EEPROM_SIZE, + .word_size = 1, + .stride = 1, + .priv = client, + .compat = true, + .base_dev = &client->dev, + }; + struct nvmem_device *ndev; + int err; + + /* Make sure we can operate on this adapter */ + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_READ_I2C_BLOCK) && + !i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_READ_BYTE_DATA)) + return -EPFNOSUPPORT; + + err = i2c_smbus_read_byte(client); + if (err < 0) + return -ENODEV; + + mutex_lock(&ee1004_bus_lock); + + err = ee1004_init_bus_data(client); + if (err < 0) { + mutex_unlock(&ee1004_bus_lock); + return err; + } + + ee1004_probe_temp_sensor(client); + + mutex_unlock(&ee1004_bus_lock); + + err = devm_add_action_or_reset(&client->dev, ee1004_cleanup_bus_data, + i2c_get_clientdata(client)); + if (err < 0) + return err; + + ndev = devm_nvmem_register(&client->dev, &config); + if (IS_ERR(ndev)) + return PTR_ERR(ndev); + + dev_info(&client->dev, + "%u byte EE1004-compliant SPD EEPROM, read-only\n", + EE1004_EEPROM_SIZE); + + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static struct i2c_driver ee1004_driver = { + .driver = { + .name = "ee1004", + }, + .probe = ee1004_probe, + .id_table = ee1004_ids, +}; +module_i2c_driver(ee1004_driver); + +MODULE_DESCRIPTION("Driver for EE1004-compliant DDR4 SPD EEPROMs"); +MODULE_AUTHOR("Jean Delvare"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/eeprom/eeprom_93cx6.c b/drivers/misc/eeprom/eeprom_93cx6.c new file mode 100644 index 000000000..e6f0e0fc1 --- /dev/null +++ b/drivers/misc/eeprom/eeprom_93cx6.c @@ -0,0 +1,383 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2004 - 2006 rt2x00 SourceForge Project + * + * + * Module: eeprom_93cx6 + * Abstract: EEPROM reader routines for 93cx6 chipsets. + * Supported chipsets: 93c46 & 93c66. + */ + +#include +#include +#include +#include +#include + +MODULE_AUTHOR("http://rt2x00.serialmonkey.com"); +MODULE_VERSION("1.0"); +MODULE_DESCRIPTION("EEPROM 93cx6 chip driver"); +MODULE_LICENSE("GPL"); + +static inline void eeprom_93cx6_pulse_high(struct eeprom_93cx6 *eeprom) +{ + eeprom->reg_data_clock = 1; + eeprom->register_write(eeprom); + + /* + * Add a short delay for the pulse to work. + * According to the specifications the "maximum minimum" + * time should be 450ns. + */ + ndelay(450); +} + +static inline void eeprom_93cx6_pulse_low(struct eeprom_93cx6 *eeprom) +{ + eeprom->reg_data_clock = 0; + eeprom->register_write(eeprom); + + /* + * Add a short delay for the pulse to work. + * According to the specifications the "maximum minimum" + * time should be 450ns. + */ + ndelay(450); +} + +static void eeprom_93cx6_startup(struct eeprom_93cx6 *eeprom) +{ + /* + * Clear all flags, and enable chip select. + */ + eeprom->register_read(eeprom); + eeprom->reg_data_in = 0; + eeprom->reg_data_out = 0; + eeprom->reg_data_clock = 0; + eeprom->reg_chip_select = 1; + eeprom->drive_data = 1; + eeprom->register_write(eeprom); + + /* + * kick a pulse. + */ + eeprom_93cx6_pulse_high(eeprom); + eeprom_93cx6_pulse_low(eeprom); +} + +static void eeprom_93cx6_cleanup(struct eeprom_93cx6 *eeprom) +{ + /* + * Clear chip_select and data_in flags. + */ + eeprom->register_read(eeprom); + eeprom->reg_data_in = 0; + eeprom->reg_chip_select = 0; + eeprom->register_write(eeprom); + + /* + * kick a pulse. + */ + eeprom_93cx6_pulse_high(eeprom); + eeprom_93cx6_pulse_low(eeprom); +} + +static void eeprom_93cx6_write_bits(struct eeprom_93cx6 *eeprom, + const u16 data, const u16 count) +{ + unsigned int i; + + eeprom->register_read(eeprom); + + /* + * Clear data flags. + */ + eeprom->reg_data_in = 0; + eeprom->reg_data_out = 0; + eeprom->drive_data = 1; + + /* + * Start writing all bits. + */ + for (i = count; i > 0; i--) { + /* + * Check if this bit needs to be set. + */ + eeprom->reg_data_in = !!(data & BIT(i - 1)); + + /* + * Write the bit to the eeprom register. + */ + eeprom->register_write(eeprom); + + /* + * Kick a pulse. + */ + eeprom_93cx6_pulse_high(eeprom); + eeprom_93cx6_pulse_low(eeprom); + } + + eeprom->reg_data_in = 0; + eeprom->register_write(eeprom); +} + +static void eeprom_93cx6_read_bits(struct eeprom_93cx6 *eeprom, + u16 *data, const u16 count) +{ + unsigned int i; + u16 buf = 0; + + eeprom->register_read(eeprom); + + /* + * Clear data flags. + */ + eeprom->reg_data_in = 0; + eeprom->reg_data_out = 0; + eeprom->drive_data = 0; + + /* + * Start reading all bits. + */ + for (i = count; i > 0; i--) { + eeprom_93cx6_pulse_high(eeprom); + + eeprom->register_read(eeprom); + + /* + * Clear data_in flag. + */ + eeprom->reg_data_in = 0; + + /* + * Read if the bit has been set. + */ + if (eeprom->reg_data_out) + buf |= BIT(i - 1); + + eeprom_93cx6_pulse_low(eeprom); + } + + *data = buf; +} + +/** + * eeprom_93cx6_read - Read a word from eeprom + * @eeprom: Pointer to eeprom structure + * @word: Word index from where we should start reading + * @data: target pointer where the information will have to be stored + * + * This function will read the eeprom data as host-endian word + * into the given data pointer. + */ +void eeprom_93cx6_read(struct eeprom_93cx6 *eeprom, const u8 word, + u16 *data) +{ + u16 command; + + /* + * Initialize the eeprom register + */ + eeprom_93cx6_startup(eeprom); + + /* + * Select the read opcode and the word to be read. + */ + command = (PCI_EEPROM_READ_OPCODE << eeprom->width) | word; + eeprom_93cx6_write_bits(eeprom, command, + PCI_EEPROM_WIDTH_OPCODE + eeprom->width); + + if (has_quirk_extra_read_cycle(eeprom)) { + eeprom_93cx6_pulse_high(eeprom); + eeprom_93cx6_pulse_low(eeprom); + } + + /* + * Read the requested 16 bits. + */ + eeprom_93cx6_read_bits(eeprom, data, 16); + + /* + * Cleanup eeprom register. + */ + eeprom_93cx6_cleanup(eeprom); +} +EXPORT_SYMBOL_GPL(eeprom_93cx6_read); + +/** + * eeprom_93cx6_multiread - Read multiple words from eeprom + * @eeprom: Pointer to eeprom structure + * @word: Word index from where we should start reading + * @data: target pointer where the information will have to be stored + * @words: Number of words that should be read. + * + * This function will read all requested words from the eeprom, + * this is done by calling eeprom_93cx6_read() multiple times. + * But with the additional change that while the eeprom_93cx6_read + * will return host ordered bytes, this method will return little + * endian words. + */ +void eeprom_93cx6_multiread(struct eeprom_93cx6 *eeprom, const u8 word, + __le16 *data, const u16 words) +{ + unsigned int i; + u16 tmp; + + for (i = 0; i < words; i++) { + tmp = 0; + eeprom_93cx6_read(eeprom, word + i, &tmp); + data[i] = cpu_to_le16(tmp); + } +} +EXPORT_SYMBOL_GPL(eeprom_93cx6_multiread); + +/** + * eeprom_93cx6_readb - Read a byte from eeprom + * @eeprom: Pointer to eeprom structure + * @byte: Byte index from where we should start reading + * @data: target pointer where the information will have to be stored + * + * This function will read a byte of the eeprom data + * into the given data pointer. + */ +void eeprom_93cx6_readb(struct eeprom_93cx6 *eeprom, const u8 byte, + u8 *data) +{ + u16 command; + u16 tmp; + + /* + * Initialize the eeprom register + */ + eeprom_93cx6_startup(eeprom); + + /* + * Select the read opcode and the byte to be read. + */ + command = (PCI_EEPROM_READ_OPCODE << (eeprom->width + 1)) | byte; + eeprom_93cx6_write_bits(eeprom, command, + PCI_EEPROM_WIDTH_OPCODE + eeprom->width + 1); + + if (has_quirk_extra_read_cycle(eeprom)) { + eeprom_93cx6_pulse_high(eeprom); + eeprom_93cx6_pulse_low(eeprom); + } + + /* + * Read the requested 8 bits. + */ + eeprom_93cx6_read_bits(eeprom, &tmp, 8); + *data = tmp & 0xff; + + /* + * Cleanup eeprom register. + */ + eeprom_93cx6_cleanup(eeprom); +} +EXPORT_SYMBOL_GPL(eeprom_93cx6_readb); + +/** + * eeprom_93cx6_multireadb - Read multiple bytes from eeprom + * @eeprom: Pointer to eeprom structure + * @byte: Index from where we should start reading + * @data: target pointer where the information will have to be stored + * @bytes: Number of bytes that should be read. + * + * This function will read all requested bytes from the eeprom, + * this is done by calling eeprom_93cx6_readb() multiple times. + */ +void eeprom_93cx6_multireadb(struct eeprom_93cx6 *eeprom, const u8 byte, + u8 *data, const u16 bytes) +{ + unsigned int i; + + for (i = 0; i < bytes; i++) + eeprom_93cx6_readb(eeprom, byte + i, &data[i]); +} +EXPORT_SYMBOL_GPL(eeprom_93cx6_multireadb); + +/** + * eeprom_93cx6_wren - set the write enable state + * @eeprom: Pointer to eeprom structure + * @enable: true to enable writes, otherwise disable writes + * + * Set the EEPROM write enable state to either allow or deny + * writes depending on the @enable value. + */ +void eeprom_93cx6_wren(struct eeprom_93cx6 *eeprom, bool enable) +{ + u16 command; + + /* start the command */ + eeprom_93cx6_startup(eeprom); + + /* create command to enable/disable */ + + command = enable ? PCI_EEPROM_EWEN_OPCODE : PCI_EEPROM_EWDS_OPCODE; + command <<= (eeprom->width - 2); + + eeprom_93cx6_write_bits(eeprom, command, + PCI_EEPROM_WIDTH_OPCODE + eeprom->width); + + eeprom_93cx6_cleanup(eeprom); +} +EXPORT_SYMBOL_GPL(eeprom_93cx6_wren); + +/** + * eeprom_93cx6_write - write data to the EEPROM + * @eeprom: Pointer to eeprom structure + * @addr: Address to write data to. + * @data: The data to write to address @addr. + * + * Write the @data to the specified @addr in the EEPROM and + * waiting for the device to finish writing. + * + * Note, since we do not expect large number of write operations + * we delay in between parts of the operation to avoid using excessive + * amounts of CPU time busy waiting. + */ +void eeprom_93cx6_write(struct eeprom_93cx6 *eeprom, u8 addr, u16 data) +{ + int timeout = 100; + u16 command; + + /* start the command */ + eeprom_93cx6_startup(eeprom); + + command = PCI_EEPROM_WRITE_OPCODE << eeprom->width; + command |= addr; + + /* send write command */ + eeprom_93cx6_write_bits(eeprom, command, + PCI_EEPROM_WIDTH_OPCODE + eeprom->width); + + /* send data */ + eeprom_93cx6_write_bits(eeprom, data, 16); + + /* get ready to check for busy */ + eeprom->drive_data = 0; + eeprom->reg_chip_select = 1; + eeprom->register_write(eeprom); + + /* wait at-least 250ns to get DO to be the busy signal */ + usleep_range(1000, 2000); + + /* wait for DO to go high to signify finish */ + + while (true) { + eeprom->register_read(eeprom); + + if (eeprom->reg_data_out) + break; + + usleep_range(1000, 2000); + + if (--timeout <= 0) { + printk(KERN_ERR "%s: timeout\n", __func__); + break; + } + } + + eeprom_93cx6_cleanup(eeprom); +} +EXPORT_SYMBOL_GPL(eeprom_93cx6_write); diff --git a/drivers/misc/eeprom/eeprom_93xx46.c b/drivers/misc/eeprom/eeprom_93xx46.c new file mode 100644 index 000000000..f9c3ab52c --- /dev/null +++ b/drivers/misc/eeprom/eeprom_93xx46.c @@ -0,0 +1,560 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for 93xx46 EEPROMs + * + * (C) 2011 DENX Software Engineering, Anatolij Gustschin + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +struct eeprom_93xx46_platform_data { + unsigned char flags; +#define EE_ADDR8 0x01 /* 8 bit addr. cfg */ +#define EE_ADDR16 0x02 /* 16 bit addr. cfg */ +#define EE_READONLY 0x08 /* forbid writing */ +#define EE_SIZE1K 0x10 /* 1 kb of data, that is a 93xx46 */ +#define EE_SIZE2K 0x20 /* 2 kb of data, that is a 93xx56 */ +#define EE_SIZE4K 0x40 /* 4 kb of data, that is a 93xx66 */ + + unsigned int quirks; +/* Single word read transfers only; no sequential read. */ +#define EEPROM_93XX46_QUIRK_SINGLE_WORD_READ (1 << 0) +/* Instructions such as EWEN are (addrlen + 2) in length. */ +#define EEPROM_93XX46_QUIRK_INSTRUCTION_LENGTH (1 << 1) +/* Add extra cycle after address during a read */ +#define EEPROM_93XX46_QUIRK_EXTRA_READ_CYCLE BIT(2) + + struct gpio_desc *select; +}; + +#define OP_START 0x4 +#define OP_WRITE (OP_START | 0x1) +#define OP_READ (OP_START | 0x2) +/* The following addresses are offset for the 1K EEPROM variant in 16-bit mode */ +#define ADDR_EWDS 0x00 +#define ADDR_ERAL 0x20 +#define ADDR_EWEN 0x30 + +struct eeprom_93xx46_devtype_data { + unsigned int quirks; + unsigned char flags; +}; + +static const struct eeprom_93xx46_devtype_data at93c46_data = { + .flags = EE_SIZE1K, +}; + +static const struct eeprom_93xx46_devtype_data at93c56_data = { + .flags = EE_SIZE2K, +}; + +static const struct eeprom_93xx46_devtype_data at93c66_data = { + .flags = EE_SIZE4K, +}; + +static const struct eeprom_93xx46_devtype_data atmel_at93c46d_data = { + .flags = EE_SIZE1K, + .quirks = EEPROM_93XX46_QUIRK_SINGLE_WORD_READ | + EEPROM_93XX46_QUIRK_INSTRUCTION_LENGTH, +}; + +static const struct eeprom_93xx46_devtype_data microchip_93lc46b_data = { + .flags = EE_SIZE1K, + .quirks = EEPROM_93XX46_QUIRK_EXTRA_READ_CYCLE, +}; + +struct eeprom_93xx46_dev { + struct spi_device *spi; + struct eeprom_93xx46_platform_data *pdata; + struct mutex lock; + struct nvmem_config nvmem_config; + struct nvmem_device *nvmem; + int addrlen; + int size; +}; + +static inline bool has_quirk_single_word_read(struct eeprom_93xx46_dev *edev) +{ + return edev->pdata->quirks & EEPROM_93XX46_QUIRK_SINGLE_WORD_READ; +} + +static inline bool has_quirk_instruction_length(struct eeprom_93xx46_dev *edev) +{ + return edev->pdata->quirks & EEPROM_93XX46_QUIRK_INSTRUCTION_LENGTH; +} + +static inline bool has_quirk_extra_read_cycle(struct eeprom_93xx46_dev *edev) +{ + return edev->pdata->quirks & EEPROM_93XX46_QUIRK_EXTRA_READ_CYCLE; +} + +static int eeprom_93xx46_read(void *priv, unsigned int off, + void *val, size_t count) +{ + struct eeprom_93xx46_dev *edev = priv; + char *buf = val; + int err = 0; + int bits; + + if (unlikely(off >= edev->size)) + return 0; + if ((off + count) > edev->size) + count = edev->size - off; + if (unlikely(!count)) + return count; + + mutex_lock(&edev->lock); + + gpiod_set_value_cansleep(edev->pdata->select, 1); + + /* The opcode in front of the address is three bits. */ + bits = edev->addrlen + 3; + + while (count) { + struct spi_message m; + struct spi_transfer t[2] = {}; + u16 cmd_addr = OP_READ << edev->addrlen; + size_t nbytes = count; + + if (edev->pdata->flags & EE_ADDR8) { + cmd_addr |= off; + if (has_quirk_single_word_read(edev)) + nbytes = 1; + } else { + cmd_addr |= (off >> 1); + if (has_quirk_single_word_read(edev)) + nbytes = 2; + } + + dev_dbg(&edev->spi->dev, "read cmd 0x%x, %d Hz\n", + cmd_addr, edev->spi->max_speed_hz); + + if (has_quirk_extra_read_cycle(edev)) { + cmd_addr <<= 1; + bits += 1; + } + + t[0].tx_buf = (char *)&cmd_addr; + t[0].len = 2; + t[0].bits_per_word = bits; + + t[1].rx_buf = buf; + t[1].len = count; + t[1].bits_per_word = 8; + + spi_message_init_with_transfers(&m, t, ARRAY_SIZE(t)); + + err = spi_sync(edev->spi, &m); + /* have to wait at least Tcsl ns */ + ndelay(250); + + if (err) { + dev_err(&edev->spi->dev, "read %zu bytes at %u: err. %d\n", + nbytes, off, err); + break; + } + + buf += nbytes; + off += nbytes; + count -= nbytes; + } + + gpiod_set_value_cansleep(edev->pdata->select, 0); + + mutex_unlock(&edev->lock); + + return err; +} + +static int eeprom_93xx46_ew(struct eeprom_93xx46_dev *edev, int is_on) +{ + struct spi_message m; + struct spi_transfer t = {}; + int bits, ret; + u16 cmd_addr; + + /* The opcode in front of the address is three bits. */ + bits = edev->addrlen + 3; + + cmd_addr = OP_START << edev->addrlen; + cmd_addr |= (is_on ? ADDR_EWEN : ADDR_EWDS) << (edev->addrlen - 6); + + if (has_quirk_instruction_length(edev)) { + cmd_addr <<= 2; + bits += 2; + } + + dev_dbg(&edev->spi->dev, "ew %s cmd 0x%04x, %d bits\n", + str_enable_disable(is_on), cmd_addr, bits); + + t.tx_buf = &cmd_addr; + t.len = 2; + t.bits_per_word = bits; + + spi_message_init_with_transfers(&m, &t, 1); + + mutex_lock(&edev->lock); + + gpiod_set_value_cansleep(edev->pdata->select, 1); + + ret = spi_sync(edev->spi, &m); + /* have to wait at least Tcsl ns */ + ndelay(250); + if (ret) + dev_err(&edev->spi->dev, "erase/write %s error %d\n", + str_enable_disable(is_on), ret); + + gpiod_set_value_cansleep(edev->pdata->select, 0); + + mutex_unlock(&edev->lock); + return ret; +} + +static ssize_t +eeprom_93xx46_write_word(struct eeprom_93xx46_dev *edev, + const char *buf, unsigned int off) +{ + struct spi_message m; + struct spi_transfer t[2] = {}; + int bits, data_len, ret; + u16 cmd_addr; + + if (unlikely(off >= edev->size)) + return -EINVAL; + + /* The opcode in front of the address is three bits. */ + bits = edev->addrlen + 3; + + cmd_addr = OP_WRITE << edev->addrlen; + + if (edev->pdata->flags & EE_ADDR8) { + cmd_addr |= off; + data_len = 1; + } else { + cmd_addr |= (off >> 1); + data_len = 2; + } + + dev_dbg(&edev->spi->dev, "write cmd 0x%x\n", cmd_addr); + + t[0].tx_buf = (char *)&cmd_addr; + t[0].len = 2; + t[0].bits_per_word = bits; + + t[1].tx_buf = buf; + t[1].len = data_len; + t[1].bits_per_word = 8; + + spi_message_init_with_transfers(&m, t, ARRAY_SIZE(t)); + + ret = spi_sync(edev->spi, &m); + /* have to wait program cycle time Twc ms */ + mdelay(6); + return ret; +} + +static int eeprom_93xx46_write(void *priv, unsigned int off, + void *val, size_t count) +{ + struct eeprom_93xx46_dev *edev = priv; + char *buf = val; + int ret, step = 1; + unsigned int i; + + if (unlikely(off >= edev->size)) + return -EFBIG; + if ((off + count) > edev->size) + count = edev->size - off; + if (unlikely(!count)) + return count; + + /* only write even number of bytes on 16-bit devices */ + if (edev->pdata->flags & EE_ADDR16) { + step = 2; + count &= ~1; + } + + /* erase/write enable */ + ret = eeprom_93xx46_ew(edev, 1); + if (ret) + return ret; + + mutex_lock(&edev->lock); + + gpiod_set_value_cansleep(edev->pdata->select, 1); + + for (i = 0; i < count; i += step) { + ret = eeprom_93xx46_write_word(edev, &buf[i], off + i); + if (ret) { + dev_err(&edev->spi->dev, "write failed at %u: %d\n", off + i, ret); + break; + } + } + + gpiod_set_value_cansleep(edev->pdata->select, 0); + + mutex_unlock(&edev->lock); + + /* erase/write disable */ + eeprom_93xx46_ew(edev, 0); + return ret; +} + +static int eeprom_93xx46_eral(struct eeprom_93xx46_dev *edev) +{ + struct spi_message m; + struct spi_transfer t = {}; + int bits, ret; + u16 cmd_addr; + + /* The opcode in front of the address is three bits. */ + bits = edev->addrlen + 3; + + cmd_addr = OP_START << edev->addrlen; + cmd_addr |= ADDR_ERAL << (edev->addrlen - 6); + + if (has_quirk_instruction_length(edev)) { + cmd_addr <<= 2; + bits += 2; + } + + dev_dbg(&edev->spi->dev, "eral cmd 0x%04x, %d bits\n", cmd_addr, bits); + + t.tx_buf = &cmd_addr; + t.len = 2; + t.bits_per_word = bits; + + spi_message_init_with_transfers(&m, &t, 1); + + mutex_lock(&edev->lock); + + gpiod_set_value_cansleep(edev->pdata->select, 1); + + ret = spi_sync(edev->spi, &m); + if (ret) + dev_err(&edev->spi->dev, "erase error %d\n", ret); + /* have to wait erase cycle time Tec ms */ + mdelay(6); + + gpiod_set_value_cansleep(edev->pdata->select, 0); + + mutex_unlock(&edev->lock); + return ret; +} + +static ssize_t erase_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct eeprom_93xx46_dev *edev = dev_get_drvdata(dev); + bool erase; + int ret; + + ret = kstrtobool(buf, &erase); + if (ret) + return ret; + + if (erase) { + ret = eeprom_93xx46_ew(edev, 1); + if (ret) + return ret; + ret = eeprom_93xx46_eral(edev); + if (ret) + return ret; + ret = eeprom_93xx46_ew(edev, 0); + if (ret) + return ret; + } + return count; +} +static DEVICE_ATTR_WO(erase); + +static const struct of_device_id eeprom_93xx46_of_table[] = { + { .compatible = "eeprom-93xx46", .data = &at93c46_data, }, + { .compatible = "atmel,at93c46", .data = &at93c46_data, }, + { .compatible = "atmel,at93c46d", .data = &atmel_at93c46d_data, }, + { .compatible = "atmel,at93c56", .data = &at93c56_data, }, + { .compatible = "atmel,at93c66", .data = &at93c66_data, }, + { .compatible = "microchip,93lc46b", .data = µchip_93lc46b_data, }, + {} +}; +MODULE_DEVICE_TABLE(of, eeprom_93xx46_of_table); + +static const struct spi_device_id eeprom_93xx46_spi_ids[] = { + { .name = "eeprom-93xx46", + .driver_data = (kernel_ulong_t)&at93c46_data, }, + { .name = "at93c46", + .driver_data = (kernel_ulong_t)&at93c46_data, }, + { .name = "at93c46d", + .driver_data = (kernel_ulong_t)&atmel_at93c46d_data, }, + { .name = "at93c56", + .driver_data = (kernel_ulong_t)&at93c56_data, }, + { .name = "at93c66", + .driver_data = (kernel_ulong_t)&at93c66_data, }, + { .name = "93lc46b", + .driver_data = (kernel_ulong_t)µchip_93lc46b_data, }, + {} +}; +MODULE_DEVICE_TABLE(spi, eeprom_93xx46_spi_ids); + +static int eeprom_93xx46_probe_fw(struct device *dev) +{ + const struct eeprom_93xx46_devtype_data *data; + struct eeprom_93xx46_platform_data *pd; + u32 tmp; + int ret; + + pd = devm_kzalloc(dev, sizeof(*pd), GFP_KERNEL); + if (!pd) + return -ENOMEM; + + ret = device_property_read_u32(dev, "data-size", &tmp); + if (ret < 0) { + dev_err(dev, "data-size property not found\n"); + return ret; + } + + if (tmp == 8) { + pd->flags |= EE_ADDR8; + } else if (tmp == 16) { + pd->flags |= EE_ADDR16; + } else { + dev_err(dev, "invalid data-size (%d)\n", tmp); + return -EINVAL; + } + + if (device_property_read_bool(dev, "read-only")) + pd->flags |= EE_READONLY; + + pd->select = devm_gpiod_get_optional(dev, "select", GPIOD_OUT_LOW); + if (IS_ERR(pd->select)) + return PTR_ERR(pd->select); + gpiod_set_consumer_name(pd->select, "93xx46 EEPROMs OE"); + + data = spi_get_device_match_data(to_spi_device(dev)); + if (data) { + pd->quirks = data->quirks; + pd->flags |= data->flags; + } + + dev->platform_data = pd; + + return 0; +} + +static int eeprom_93xx46_probe(struct spi_device *spi) +{ + struct eeprom_93xx46_platform_data *pd; + struct eeprom_93xx46_dev *edev; + struct device *dev = &spi->dev; + int err; + + err = eeprom_93xx46_probe_fw(dev); + if (err < 0) + return err; + + pd = spi->dev.platform_data; + if (!pd) { + dev_err(&spi->dev, "missing platform data\n"); + return -ENODEV; + } + + edev = devm_kzalloc(&spi->dev, sizeof(*edev), GFP_KERNEL); + if (!edev) + return -ENOMEM; + + if (pd->flags & EE_SIZE1K) + edev->size = 128; + else if (pd->flags & EE_SIZE2K) + edev->size = 256; + else if (pd->flags & EE_SIZE4K) + edev->size = 512; + else { + dev_err(&spi->dev, "unspecified size\n"); + return -EINVAL; + } + + if (pd->flags & EE_ADDR8) + edev->addrlen = ilog2(edev->size); + else if (pd->flags & EE_ADDR16) + edev->addrlen = ilog2(edev->size) - 1; + else { + dev_err(&spi->dev, "unspecified address type\n"); + return -EINVAL; + } + + mutex_init(&edev->lock); + + edev->spi = spi; + edev->pdata = pd; + + edev->nvmem_config.type = NVMEM_TYPE_EEPROM; + edev->nvmem_config.name = dev_name(&spi->dev); + edev->nvmem_config.dev = &spi->dev; + edev->nvmem_config.read_only = pd->flags & EE_READONLY; + edev->nvmem_config.root_only = true; + edev->nvmem_config.owner = THIS_MODULE; + edev->nvmem_config.compat = true; + edev->nvmem_config.base_dev = &spi->dev; + edev->nvmem_config.reg_read = eeprom_93xx46_read; + edev->nvmem_config.reg_write = eeprom_93xx46_write; + edev->nvmem_config.priv = edev; + edev->nvmem_config.stride = 4; + edev->nvmem_config.word_size = 1; + edev->nvmem_config.size = edev->size; + + edev->nvmem = devm_nvmem_register(&spi->dev, &edev->nvmem_config); + if (IS_ERR(edev->nvmem)) + return PTR_ERR(edev->nvmem); + + dev_info(&spi->dev, "%d-bit eeprom containing %d bytes %s\n", + (pd->flags & EE_ADDR8) ? 8 : 16, + edev->size, + (pd->flags & EE_READONLY) ? "(readonly)" : ""); + + if (!(pd->flags & EE_READONLY)) { + if (device_create_file(&spi->dev, &dev_attr_erase)) + dev_err(&spi->dev, "can't create erase interface\n"); + } + + spi_set_drvdata(spi, edev); + return 0; +} + +static void eeprom_93xx46_remove(struct spi_device *spi) +{ + struct eeprom_93xx46_dev *edev = spi_get_drvdata(spi); + + if (!(edev->pdata->flags & EE_READONLY)) + device_remove_file(&spi->dev, &dev_attr_erase); +} + +static struct spi_driver eeprom_93xx46_driver = { + .driver = { + .name = "93xx46", + .of_match_table = eeprom_93xx46_of_table, + }, + .probe = eeprom_93xx46_probe, + .remove = eeprom_93xx46_remove, + .id_table = eeprom_93xx46_spi_ids, +}; + +module_spi_driver(eeprom_93xx46_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Driver for 93xx46 EEPROMs"); +MODULE_AUTHOR("Anatolij Gustschin "); +MODULE_ALIAS("spi:93xx46"); diff --git a/drivers/misc/eeprom/idt_89hpesx.c b/drivers/misc/eeprom/idt_89hpesx.c new file mode 100644 index 000000000..e056d2dea --- /dev/null +++ b/drivers/misc/eeprom/idt_89hpesx.c @@ -0,0 +1,1515 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2016 T-Platforms. All Rights Reserved. + * + * IDT PCIe-switch NTB Linux driver + * + * Contact Information: + * Serge Semin , + */ +/* + * NOTE of the IDT 89HPESx SMBus-slave interface driver + * This driver primarily is developed to have an access to EEPROM device of + * IDT PCIe-switches. IDT provides a simple SMBus interface to perform IO- + * operations from/to EEPROM, which is located at private (so called Master) + * SMBus of switches. Using that interface this the driver creates a simple + * binary sysfs-file in the device directory: + * /sys/bus/i2c/devices/-/eeprom + * In case if read-only flag is specified in the dts-node of device desription, + * User-space applications won't be able to write to the EEPROM sysfs-node. + * Additionally IDT 89HPESx SMBus interface has an ability to write/read + * data of device CSRs. This driver exposes debugf-file to perform simple IO + * operations using that ability for just basic debug purpose. Particularly + * next file is created in the specific debugfs-directory: + * /sys/kernel/debug/idt_csr/ + * Format of the debugfs-node is: + * $ cat /sys/kernel/debug/idt_csr/-/; + * : + * So reading the content of the file gives current CSR address and it value. + * If User-space application wishes to change current CSR address, + * it can just write a proper value to the sysfs-file: + * $ echo "" > /sys/kernel/debug/idt_csr/-/ + * If it wants to change the CSR value as well, the format of the write + * operation is: + * $ echo ":" > \ + * /sys/kernel/debug/idt_csr/-/; + * CSR address and value can be any of hexadecimal, decimal or octal format. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IDT_NAME "89hpesx" +#define IDT_89HPESX_DESC "IDT 89HPESx SMBus-slave interface driver" +#define IDT_89HPESX_VER "1.0" + +MODULE_DESCRIPTION(IDT_89HPESX_DESC); +MODULE_VERSION(IDT_89HPESX_VER); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("T-platforms"); + +/* + * struct idt_89hpesx_dev - IDT 89HPESx device data structure + * @eesize: Size of EEPROM in bytes (calculated from "idt,eecompatible") + * @eero: EEPROM Read-only flag + * @eeaddr: EEPROM custom address + * + * @inieecmd: Initial cmd value for EEPROM read/write operations + * @inicsrcmd: Initial cmd value for CSR read/write operations + * @iniccode: Initialial command code value for IO-operations + * + * @csr: CSR address to perform read operation + * + * @smb_write: SMBus write method + * @smb_read: SMBus read method + * @smb_mtx: SMBus mutex + * + * @client: i2c client used to perform IO operations + * + * @ee_file: EEPROM read/write sysfs-file + */ +struct idt_smb_seq; +struct idt_89hpesx_dev { + u32 eesize; + bool eero; + u8 eeaddr; + + u8 inieecmd; + u8 inicsrcmd; + u8 iniccode; + + u16 csr; + + int (*smb_write)(struct idt_89hpesx_dev *, const struct idt_smb_seq *); + int (*smb_read)(struct idt_89hpesx_dev *, struct idt_smb_seq *); + struct mutex smb_mtx; + + struct i2c_client *client; + + struct bin_attribute *ee_file; + struct dentry *csr_dir; +}; + +/* + * struct idt_smb_seq - sequence of data to be read/written from/to IDT 89HPESx + * @ccode: SMBus command code + * @bytecnt: Byte count of operation + * @data: Data to by written + */ +struct idt_smb_seq { + u8 ccode; + u8 bytecnt; + u8 *data; +}; + +/* + * struct idt_eeprom_seq - sequence of data to be read/written from/to EEPROM + * @cmd: Transaction CMD + * @eeaddr: EEPROM custom address + * @memaddr: Internal memory address of EEPROM + * @data: Data to be written at the memory address + */ +struct idt_eeprom_seq { + u8 cmd; + u8 eeaddr; + __le16 memaddr; + u8 data; +} __packed; + +/* + * struct idt_csr_seq - sequence of data to be read/written from/to CSR + * @cmd: Transaction CMD + * @csraddr: Internal IDT device CSR address + * @data: Data to be read/written from/to the CSR address + */ +struct idt_csr_seq { + u8 cmd; + __le16 csraddr; + __le32 data; +} __packed; + +/* + * SMBus command code macros + * @CCODE_END: Indicates the end of transaction + * @CCODE_START: Indicates the start of transaction + * @CCODE_CSR: CSR read/write transaction + * @CCODE_EEPROM: EEPROM read/write transaction + * @CCODE_BYTE: Supplied data has BYTE length + * @CCODE_WORD: Supplied data has WORD length + * @CCODE_BLOCK: Supplied data has variable length passed in bytecnt + * byte right following CCODE byte + */ +#define CCODE_END ((u8)0x01) +#define CCODE_START ((u8)0x02) +#define CCODE_CSR ((u8)0x00) +#define CCODE_EEPROM ((u8)0x04) +#define CCODE_BYTE ((u8)0x00) +#define CCODE_WORD ((u8)0x20) +#define CCODE_BLOCK ((u8)0x40) +#define CCODE_PEC ((u8)0x80) + +/* + * EEPROM command macros + * @EEPROM_OP_WRITE: EEPROM write operation + * @EEPROM_OP_READ: EEPROM read operation + * @EEPROM_USA: Use specified address of EEPROM + * @EEPROM_NAERR: EEPROM device is not ready to respond + * @EEPROM_LAERR: EEPROM arbitration loss error + * @EEPROM_MSS: EEPROM misplace start & stop bits error + * @EEPROM_WR_CNT: Bytes count to perform write operation + * @EEPROM_WRRD_CNT: Bytes count to write before reading + * @EEPROM_RD_CNT: Bytes count to perform read operation + * @EEPROM_DEF_SIZE: Fall back size of EEPROM + * @EEPROM_DEF_ADDR: Defatul EEPROM address + * @EEPROM_TOUT: Timeout before retry read operation if eeprom is busy + */ +#define EEPROM_OP_WRITE ((u8)0x00) +#define EEPROM_OP_READ ((u8)0x01) +#define EEPROM_USA ((u8)0x02) +#define EEPROM_NAERR ((u8)0x08) +#define EEPROM_LAERR ((u8)0x10) +#define EEPROM_MSS ((u8)0x20) +#define EEPROM_WR_CNT ((u8)5) +#define EEPROM_WRRD_CNT ((u8)4) +#define EEPROM_RD_CNT ((u8)5) +#define EEPROM_DEF_SIZE ((u16)4096) +#define EEPROM_DEF_ADDR ((u8)0x50) +#define EEPROM_TOUT (100) + +/* + * CSR command macros + * @CSR_DWE: Enable all four bytes of the operation + * @CSR_OP_WRITE: CSR write operation + * @CSR_OP_READ: CSR read operation + * @CSR_RERR: Read operation error + * @CSR_WERR: Write operation error + * @CSR_WR_CNT: Bytes count to perform write operation + * @CSR_WRRD_CNT: Bytes count to write before reading + * @CSR_RD_CNT: Bytes count to perform read operation + * @CSR_MAX: Maximum CSR address + * @CSR_DEF: Default CSR address + * @CSR_REAL_ADDR: CSR real unshifted address + */ +#define CSR_DWE ((u8)0x0F) +#define CSR_OP_WRITE ((u8)0x00) +#define CSR_OP_READ ((u8)0x10) +#define CSR_RERR ((u8)0x40) +#define CSR_WERR ((u8)0x80) +#define CSR_WR_CNT ((u8)7) +#define CSR_WRRD_CNT ((u8)3) +#define CSR_RD_CNT ((u8)7) +#define CSR_MAX ((u32)0x3FFFF) +#define CSR_DEF ((u16)0x0000) +#define CSR_REAL_ADDR(val) ((unsigned int)val << 2) + +/* + * IDT 89HPESx basic register + * @IDT_VIDDID_CSR: PCIe VID and DID of IDT 89HPESx + * @IDT_VID_MASK: Mask of VID + */ +#define IDT_VIDDID_CSR ((u32)0x0000) +#define IDT_VID_MASK ((u32)0xFFFF) + +/* + * IDT 89HPESx can send NACK when new command is sent before previous one + * fininshed execution. In this case driver retries operation + * certain times. + * @RETRY_CNT: Number of retries before giving up and fail + * @idt_smb_safe: Generate a retry loop on corresponding SMBus method + */ +#define RETRY_CNT (128) +#define idt_smb_safe(ops, args...) ({ \ + int __retry = RETRY_CNT; \ + s32 __sts; \ + do { \ + __sts = i2c_smbus_ ## ops ## _data(args); \ + } while (__retry-- && __sts < 0); \ + __sts; \ +}) + +/*=========================================================================== + * i2c bus level IO-operations + *=========================================================================== + */ + +/* + * idt_smb_write_byte() - SMBus write method when I2C_SMBUS_BYTE_DATA operation + * is only available + * @pdev: Pointer to the driver data + * @seq: Sequence of data to be written + */ +static int idt_smb_write_byte(struct idt_89hpesx_dev *pdev, + const struct idt_smb_seq *seq) +{ + s32 sts; + u8 ccode; + int idx; + + /* Loop over the supplied data sending byte one-by-one */ + for (idx = 0; idx < seq->bytecnt; idx++) { + /* Collect the command code byte */ + ccode = seq->ccode | CCODE_BYTE; + if (idx == 0) + ccode |= CCODE_START; + if (idx == seq->bytecnt - 1) + ccode |= CCODE_END; + + /* Send data to the device */ + sts = idt_smb_safe(write_byte, pdev->client, ccode, + seq->data[idx]); + if (sts != 0) + return (int)sts; + } + + return 0; +} + +/* + * idt_smb_read_byte() - SMBus read method when I2C_SMBUS_BYTE_DATA operation + * is only available + * @pdev: Pointer to the driver data + * @seq: Buffer to read data to + */ +static int idt_smb_read_byte(struct idt_89hpesx_dev *pdev, + struct idt_smb_seq *seq) +{ + s32 sts; + u8 ccode; + int idx; + + /* Loop over the supplied buffer receiving byte one-by-one */ + for (idx = 0; idx < seq->bytecnt; idx++) { + /* Collect the command code byte */ + ccode = seq->ccode | CCODE_BYTE; + if (idx == 0) + ccode |= CCODE_START; + if (idx == seq->bytecnt - 1) + ccode |= CCODE_END; + + /* Read data from the device */ + sts = idt_smb_safe(read_byte, pdev->client, ccode); + if (sts < 0) + return (int)sts; + + seq->data[idx] = (u8)sts; + } + + return 0; +} + +/* + * idt_smb_write_word() - SMBus write method when I2C_SMBUS_BYTE_DATA and + * I2C_FUNC_SMBUS_WORD_DATA operations are available + * @pdev: Pointer to the driver data + * @seq: Sequence of data to be written + */ +static int idt_smb_write_word(struct idt_89hpesx_dev *pdev, + const struct idt_smb_seq *seq) +{ + s32 sts; + u8 ccode; + int idx, evencnt; + + /* Calculate the even count of data to send */ + evencnt = seq->bytecnt - (seq->bytecnt % 2); + + /* Loop over the supplied data sending two bytes at a time */ + for (idx = 0; idx < evencnt; idx += 2) { + /* Collect the command code byte */ + ccode = seq->ccode | CCODE_WORD; + if (idx == 0) + ccode |= CCODE_START; + if (idx == evencnt - 2) + ccode |= CCODE_END; + + /* Send word data to the device */ + sts = idt_smb_safe(write_word, pdev->client, ccode, + *(u16 *)&seq->data[idx]); + if (sts != 0) + return (int)sts; + } + + /* If there is odd number of bytes then send just one last byte */ + if (seq->bytecnt != evencnt) { + /* Collect the command code byte */ + ccode = seq->ccode | CCODE_BYTE | CCODE_END; + if (idx == 0) + ccode |= CCODE_START; + + /* Send byte data to the device */ + sts = idt_smb_safe(write_byte, pdev->client, ccode, + seq->data[idx]); + if (sts != 0) + return (int)sts; + } + + return 0; +} + +/* + * idt_smb_read_word() - SMBus read method when I2C_SMBUS_BYTE_DATA and + * I2C_FUNC_SMBUS_WORD_DATA operations are available + * @pdev: Pointer to the driver data + * @seq: Buffer to read data to + */ +static int idt_smb_read_word(struct idt_89hpesx_dev *pdev, + struct idt_smb_seq *seq) +{ + s32 sts; + u8 ccode; + int idx, evencnt; + + /* Calculate the even count of data to send */ + evencnt = seq->bytecnt - (seq->bytecnt % 2); + + /* Loop over the supplied data reading two bytes at a time */ + for (idx = 0; idx < evencnt; idx += 2) { + /* Collect the command code byte */ + ccode = seq->ccode | CCODE_WORD; + if (idx == 0) + ccode |= CCODE_START; + if (idx == evencnt - 2) + ccode |= CCODE_END; + + /* Read word data from the device */ + sts = idt_smb_safe(read_word, pdev->client, ccode); + if (sts < 0) + return (int)sts; + + *(u16 *)&seq->data[idx] = (u16)sts; + } + + /* If there is odd number of bytes then receive just one last byte */ + if (seq->bytecnt != evencnt) { + /* Collect the command code byte */ + ccode = seq->ccode | CCODE_BYTE | CCODE_END; + if (idx == 0) + ccode |= CCODE_START; + + /* Read last data byte from the device */ + sts = idt_smb_safe(read_byte, pdev->client, ccode); + if (sts < 0) + return (int)sts; + + seq->data[idx] = (u8)sts; + } + + return 0; +} + +/* + * idt_smb_write_block() - SMBus write method when I2C_SMBUS_BLOCK_DATA + * operation is available + * @pdev: Pointer to the driver data + * @seq: Sequence of data to be written + */ +static int idt_smb_write_block(struct idt_89hpesx_dev *pdev, + const struct idt_smb_seq *seq) +{ + u8 ccode; + + /* Return error if too much data passed to send */ + if (seq->bytecnt > I2C_SMBUS_BLOCK_MAX) + return -EINVAL; + + /* Collect the command code byte */ + ccode = seq->ccode | CCODE_BLOCK | CCODE_START | CCODE_END; + + /* Send block of data to the device */ + return idt_smb_safe(write_block, pdev->client, ccode, seq->bytecnt, + seq->data); +} + +/* + * idt_smb_read_block() - SMBus read method when I2C_SMBUS_BLOCK_DATA + * operation is available + * @pdev: Pointer to the driver data + * @seq: Buffer to read data to + */ +static int idt_smb_read_block(struct idt_89hpesx_dev *pdev, + struct idt_smb_seq *seq) +{ + s32 sts; + u8 ccode; + + /* Return error if too much data passed to send */ + if (seq->bytecnt > I2C_SMBUS_BLOCK_MAX) + return -EINVAL; + + /* Collect the command code byte */ + ccode = seq->ccode | CCODE_BLOCK | CCODE_START | CCODE_END; + + /* Read block of data from the device */ + sts = idt_smb_safe(read_block, pdev->client, ccode, seq->data); + if (sts != seq->bytecnt) + return (sts < 0 ? sts : -ENODATA); + + return 0; +} + +/* + * idt_smb_write_i2c_block() - SMBus write method when I2C_SMBUS_I2C_BLOCK_DATA + * operation is available + * @pdev: Pointer to the driver data + * @seq: Sequence of data to be written + * + * NOTE It's usual SMBus write block operation, except the actual data length is + * sent as first byte of data + */ +static int idt_smb_write_i2c_block(struct idt_89hpesx_dev *pdev, + const struct idt_smb_seq *seq) +{ + u8 ccode, buf[I2C_SMBUS_BLOCK_MAX + 1]; + + /* Return error if too much data passed to send */ + if (seq->bytecnt > I2C_SMBUS_BLOCK_MAX) + return -EINVAL; + + /* Collect the data to send. Length byte must be added prior the data */ + buf[0] = seq->bytecnt; + memcpy(&buf[1], seq->data, seq->bytecnt); + + /* Collect the command code byte */ + ccode = seq->ccode | CCODE_BLOCK | CCODE_START | CCODE_END; + + /* Send length and block of data to the device */ + return idt_smb_safe(write_i2c_block, pdev->client, ccode, + seq->bytecnt + 1, buf); +} + +/* + * idt_smb_read_i2c_block() - SMBus read method when I2C_SMBUS_I2C_BLOCK_DATA + * operation is available + * @pdev: Pointer to the driver data + * @seq: Buffer to read data to + * + * NOTE It's usual SMBus read block operation, except the actual data length is + * retrieved as first byte of data + */ +static int idt_smb_read_i2c_block(struct idt_89hpesx_dev *pdev, + struct idt_smb_seq *seq) +{ + u8 ccode, buf[I2C_SMBUS_BLOCK_MAX + 1]; + s32 sts; + + /* Return error if too much data passed to send */ + if (seq->bytecnt > I2C_SMBUS_BLOCK_MAX) + return -EINVAL; + + /* Collect the command code byte */ + ccode = seq->ccode | CCODE_BLOCK | CCODE_START | CCODE_END; + + /* Read length and block of data from the device */ + sts = idt_smb_safe(read_i2c_block, pdev->client, ccode, + seq->bytecnt + 1, buf); + if (sts != seq->bytecnt + 1) + return (sts < 0 ? sts : -ENODATA); + if (buf[0] != seq->bytecnt) + return -ENODATA; + + /* Copy retrieved data to the output data buffer */ + memcpy(seq->data, &buf[1], seq->bytecnt); + + return 0; +} + +/*=========================================================================== + * EEPROM IO-operations + *=========================================================================== + */ + +/* + * idt_eeprom_read_byte() - read just one byte from EEPROM + * @pdev: Pointer to the driver data + * @memaddr: Start EEPROM memory address + * @data: Data to be written to EEPROM + */ +static int idt_eeprom_read_byte(struct idt_89hpesx_dev *pdev, u16 memaddr, + u8 *data) +{ + struct device *dev = &pdev->client->dev; + struct idt_eeprom_seq eeseq; + struct idt_smb_seq smbseq; + int ret, retry; + + /* Initialize SMBus sequence fields */ + smbseq.ccode = pdev->iniccode | CCODE_EEPROM; + smbseq.data = (u8 *)&eeseq; + + /* + * Sometimes EEPROM may respond with NACK if it's busy with previous + * operation, so we need to perform a few attempts of read cycle + */ + retry = RETRY_CNT; + do { + /* Send EEPROM memory address to read data from */ + smbseq.bytecnt = EEPROM_WRRD_CNT; + eeseq.cmd = pdev->inieecmd | EEPROM_OP_READ; + eeseq.eeaddr = pdev->eeaddr; + eeseq.memaddr = cpu_to_le16(memaddr); + ret = pdev->smb_write(pdev, &smbseq); + if (ret != 0) { + dev_err(dev, "Failed to init eeprom addr 0x%02x", + memaddr); + break; + } + + /* Perform read operation */ + smbseq.bytecnt = EEPROM_RD_CNT; + ret = pdev->smb_read(pdev, &smbseq); + if (ret != 0) { + dev_err(dev, "Failed to read eeprom data 0x%02x", + memaddr); + break; + } + + /* Restart read operation if the device is busy */ + if (retry && (eeseq.cmd & EEPROM_NAERR)) { + dev_dbg(dev, "EEPROM busy, retry reading after %d ms", + EEPROM_TOUT); + msleep(EEPROM_TOUT); + continue; + } + + /* Check whether IDT successfully read data from EEPROM */ + if (eeseq.cmd & (EEPROM_NAERR | EEPROM_LAERR | EEPROM_MSS)) { + dev_err(dev, + "Communication with eeprom failed, cmd 0x%hhx", + eeseq.cmd); + ret = -EREMOTEIO; + break; + } + + /* Save retrieved data and exit the loop */ + *data = eeseq.data; + break; + } while (retry--); + + /* Return the status of operation */ + return ret; +} + +/* + * idt_eeprom_write() - EEPROM write operation + * @pdev: Pointer to the driver data + * @memaddr: Start EEPROM memory address + * @len: Length of data to be written + * @data: Data to be written to EEPROM + */ +static int idt_eeprom_write(struct idt_89hpesx_dev *pdev, u16 memaddr, u16 len, + const u8 *data) +{ + struct device *dev = &pdev->client->dev; + struct idt_eeprom_seq eeseq; + struct idt_smb_seq smbseq; + int ret; + u16 idx; + + /* Initialize SMBus sequence fields */ + smbseq.ccode = pdev->iniccode | CCODE_EEPROM; + smbseq.data = (u8 *)&eeseq; + + /* Send data byte-by-byte, checking if it is successfully written */ + for (idx = 0; idx < len; idx++, memaddr++) { + /* Lock IDT SMBus device */ + mutex_lock(&pdev->smb_mtx); + + /* Perform write operation */ + smbseq.bytecnt = EEPROM_WR_CNT; + eeseq.cmd = pdev->inieecmd | EEPROM_OP_WRITE; + eeseq.eeaddr = pdev->eeaddr; + eeseq.memaddr = cpu_to_le16(memaddr); + eeseq.data = data[idx]; + ret = pdev->smb_write(pdev, &smbseq); + if (ret != 0) { + dev_err(dev, + "Failed to write 0x%04hx:0x%02hhx to eeprom", + memaddr, data[idx]); + goto err_mutex_unlock; + } + + /* + * Check whether the data is successfully written by reading + * from the same EEPROM memory address. + */ + eeseq.data = ~data[idx]; + ret = idt_eeprom_read_byte(pdev, memaddr, &eeseq.data); + if (ret != 0) + goto err_mutex_unlock; + + /* Check whether the read byte is the same as written one */ + if (eeseq.data != data[idx]) { + dev_err(dev, "Values don't match 0x%02hhx != 0x%02hhx", + eeseq.data, data[idx]); + ret = -EREMOTEIO; + goto err_mutex_unlock; + } + + /* Unlock IDT SMBus device */ +err_mutex_unlock: + mutex_unlock(&pdev->smb_mtx); + if (ret != 0) + return ret; + } + + return 0; +} + +/* + * idt_eeprom_read() - EEPROM read operation + * @pdev: Pointer to the driver data + * @memaddr: Start EEPROM memory address + * @len: Length of data to read + * @buf: Buffer to read data to + */ +static int idt_eeprom_read(struct idt_89hpesx_dev *pdev, u16 memaddr, u16 len, + u8 *buf) +{ + int ret; + u16 idx; + + /* Read data byte-by-byte, retrying if it wasn't successful */ + for (idx = 0; idx < len; idx++, memaddr++) { + /* Lock IDT SMBus device */ + mutex_lock(&pdev->smb_mtx); + + /* Just read the byte to the buffer */ + ret = idt_eeprom_read_byte(pdev, memaddr, &buf[idx]); + + /* Unlock IDT SMBus device */ + mutex_unlock(&pdev->smb_mtx); + + /* Return error if read operation failed */ + if (ret != 0) + return ret; + } + + return 0; +} + +/*=========================================================================== + * CSR IO-operations + *=========================================================================== + */ + +/* + * idt_csr_write() - CSR write operation + * @pdev: Pointer to the driver data + * @csraddr: CSR address (with no two LS bits) + * @data: Data to be written to CSR + */ +static int idt_csr_write(struct idt_89hpesx_dev *pdev, u16 csraddr, + const u32 data) +{ + struct device *dev = &pdev->client->dev; + struct idt_csr_seq csrseq; + struct idt_smb_seq smbseq; + int ret; + + /* Initialize SMBus sequence fields */ + smbseq.ccode = pdev->iniccode | CCODE_CSR; + smbseq.data = (u8 *)&csrseq; + + /* Lock IDT SMBus device */ + mutex_lock(&pdev->smb_mtx); + + /* Perform write operation */ + smbseq.bytecnt = CSR_WR_CNT; + csrseq.cmd = pdev->inicsrcmd | CSR_OP_WRITE; + csrseq.csraddr = cpu_to_le16(csraddr); + csrseq.data = cpu_to_le32(data); + ret = pdev->smb_write(pdev, &smbseq); + if (ret != 0) { + dev_err(dev, "Failed to write 0x%04x: 0x%04x to csr", + CSR_REAL_ADDR(csraddr), data); + goto err_mutex_unlock; + } + + /* Send CSR address to read data from */ + smbseq.bytecnt = CSR_WRRD_CNT; + csrseq.cmd = pdev->inicsrcmd | CSR_OP_READ; + ret = pdev->smb_write(pdev, &smbseq); + if (ret != 0) { + dev_err(dev, "Failed to init csr address 0x%04x", + CSR_REAL_ADDR(csraddr)); + goto err_mutex_unlock; + } + + /* Perform read operation */ + smbseq.bytecnt = CSR_RD_CNT; + ret = pdev->smb_read(pdev, &smbseq); + if (ret != 0) { + dev_err(dev, "Failed to read csr 0x%04x", + CSR_REAL_ADDR(csraddr)); + goto err_mutex_unlock; + } + + /* Check whether IDT successfully retrieved CSR data */ + if (csrseq.cmd & (CSR_RERR | CSR_WERR)) { + dev_err(dev, "IDT failed to perform CSR r/w"); + ret = -EREMOTEIO; + goto err_mutex_unlock; + } + + /* Unlock IDT SMBus device */ +err_mutex_unlock: + mutex_unlock(&pdev->smb_mtx); + + return ret; +} + +/* + * idt_csr_read() - CSR read operation + * @pdev: Pointer to the driver data + * @csraddr: CSR address (with no two LS bits) + * @data: Data to be written to CSR + */ +static int idt_csr_read(struct idt_89hpesx_dev *pdev, u16 csraddr, u32 *data) +{ + struct device *dev = &pdev->client->dev; + struct idt_csr_seq csrseq; + struct idt_smb_seq smbseq; + int ret; + + /* Initialize SMBus sequence fields */ + smbseq.ccode = pdev->iniccode | CCODE_CSR; + smbseq.data = (u8 *)&csrseq; + + /* Lock IDT SMBus device */ + mutex_lock(&pdev->smb_mtx); + + /* Send CSR register address before reading it */ + smbseq.bytecnt = CSR_WRRD_CNT; + csrseq.cmd = pdev->inicsrcmd | CSR_OP_READ; + csrseq.csraddr = cpu_to_le16(csraddr); + ret = pdev->smb_write(pdev, &smbseq); + if (ret != 0) { + dev_err(dev, "Failed to init csr address 0x%04x", + CSR_REAL_ADDR(csraddr)); + goto err_mutex_unlock; + } + + /* Perform read operation */ + smbseq.bytecnt = CSR_RD_CNT; + ret = pdev->smb_read(pdev, &smbseq); + if (ret != 0) { + dev_err(dev, "Failed to read csr 0x%04x", + CSR_REAL_ADDR(csraddr)); + goto err_mutex_unlock; + } + + /* Check whether IDT successfully retrieved CSR data */ + if (csrseq.cmd & (CSR_RERR | CSR_WERR)) { + dev_err(dev, "IDT failed to perform CSR r/w"); + ret = -EREMOTEIO; + goto err_mutex_unlock; + } + + /* Save data retrieved from IDT */ + *data = le32_to_cpu(csrseq.data); + + /* Unlock IDT SMBus device */ +err_mutex_unlock: + mutex_unlock(&pdev->smb_mtx); + + return ret; +} + +/*=========================================================================== + * Sysfs/debugfs-nodes IO-operations + *=========================================================================== + */ + +/* + * eeprom_write() - EEPROM sysfs-node write callback + * @filep: Pointer to the file system node + * @kobj: Pointer to the kernel object related to the sysfs-node + * @attr: Attributes of the file + * @buf: Buffer to write data to + * @off: Offset at which data should be written to + * @count: Number of bytes to write + */ +static ssize_t eeprom_write(struct file *filp, struct kobject *kobj, + const struct bin_attribute *attr, + char *buf, loff_t off, size_t count) +{ + struct idt_89hpesx_dev *pdev; + int ret; + + /* Retrieve driver data */ + pdev = dev_get_drvdata(kobj_to_dev(kobj)); + + /* Perform EEPROM write operation */ + ret = idt_eeprom_write(pdev, (u16)off, (u16)count, (u8 *)buf); + return (ret != 0 ? ret : count); +} + +/* + * eeprom_read() - EEPROM sysfs-node read callback + * @filep: Pointer to the file system node + * @kobj: Pointer to the kernel object related to the sysfs-node + * @attr: Attributes of the file + * @buf: Buffer to write data to + * @off: Offset at which data should be written to + * @count: Number of bytes to write + */ +static ssize_t eeprom_read(struct file *filp, struct kobject *kobj, + const struct bin_attribute *attr, + char *buf, loff_t off, size_t count) +{ + struct idt_89hpesx_dev *pdev; + int ret; + + /* Retrieve driver data */ + pdev = dev_get_drvdata(kobj_to_dev(kobj)); + + /* Perform EEPROM read operation */ + ret = idt_eeprom_read(pdev, (u16)off, (u16)count, (u8 *)buf); + return (ret != 0 ? ret : count); +} + +/* + * idt_dbgfs_csr_write() - CSR debugfs-node write callback + * @filep: Pointer to the file system file descriptor + * @buf: Buffer to read data from + * @count: Size of the buffer + * @offp: Offset within the file + * + * It accepts either "0x:0x" for saving register address + * and writing value to specified DWORD register or "0x" for + * just saving register address in order to perform next read operation. + * + * WARNING No spaces are allowed. Incoming string must be strictly formated as: + * ":". Register address must be aligned within 4 bytes + * (one DWORD). + */ +static ssize_t idt_dbgfs_csr_write(struct file *filep, const char __user *ubuf, + size_t count, loff_t *offp) +{ + struct idt_89hpesx_dev *pdev = filep->private_data; + char *colon_ch, *csraddr_str, *csrval_str; + int ret; + u32 csraddr, csrval; + char *buf; + + if (*offp) + return 0; + + /* Copy data from User-space */ + buf = memdup_user_nul(ubuf, count); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + /* Find position of colon in the buffer */ + colon_ch = strnchr(buf, count, ':'); + + /* + * If there is colon passed then new CSR value should be parsed as + * well, so allocate buffer for CSR address substring. + * If no colon is found, then string must have just one number with + * no new CSR value + */ + if (colon_ch != NULL) { + /* Copy the register address to the substring buffer */ + csraddr_str = kmemdup_nul(buf, colon_ch - buf, GFP_KERNEL); + if (csraddr_str == NULL) { + ret = -ENOMEM; + goto free_buf; + } + /* Register value must follow the colon */ + csrval_str = colon_ch + 1; + } else /* if (str_colon == NULL) */ { + csraddr_str = (char *)buf; /* Just to shut warning up */ + csrval_str = NULL; + } + + /* Convert CSR address to u32 value */ + ret = kstrtou32(csraddr_str, 0, &csraddr); + if (ret != 0) + goto free_csraddr_str; + + /* Check whether passed register address is valid */ + if (csraddr > CSR_MAX || !IS_ALIGNED(csraddr, SZ_4)) { + ret = -EINVAL; + goto free_csraddr_str; + } + + /* Shift register address to the right so to have u16 address */ + pdev->csr = (csraddr >> 2); + + /* Parse new CSR value and send it to IDT, if colon has been found */ + if (colon_ch != NULL) { + ret = kstrtou32(csrval_str, 0, &csrval); + if (ret != 0) + goto free_csraddr_str; + + ret = idt_csr_write(pdev, pdev->csr, csrval); + if (ret != 0) + goto free_csraddr_str; + } + + /* Free memory only if colon has been found */ +free_csraddr_str: + if (colon_ch != NULL) + kfree(csraddr_str); + + /* Free buffer allocated for data retrieved from User-space */ +free_buf: + kfree(buf); + + return (ret != 0 ? ret : count); +} + +/* + * idt_dbgfs_csr_read() - CSR debugfs-node read callback + * @filep: Pointer to the file system file descriptor + * @buf: Buffer to write data to + * @count: Size of the buffer + * @offp: Offset within the file + * + * It just prints the pair "0x:0x" to passed buffer. + */ +#define CSRBUF_SIZE ((size_t)32) +static ssize_t idt_dbgfs_csr_read(struct file *filep, char __user *ubuf, + size_t count, loff_t *offp) +{ + struct idt_89hpesx_dev *pdev = filep->private_data; + u32 csraddr, csrval; + char buf[CSRBUF_SIZE]; + int ret, size; + + /* Perform CSR read operation */ + ret = idt_csr_read(pdev, pdev->csr, &csrval); + if (ret != 0) + return ret; + + /* Shift register address to the left so to have real address */ + csraddr = ((u32)pdev->csr << 2); + + /* Print the "0x:0x" to buffer */ + size = snprintf(buf, CSRBUF_SIZE, "0x%05x:0x%08x\n", + (unsigned int)csraddr, (unsigned int)csrval); + + /* Copy data to User-space */ + return simple_read_from_buffer(ubuf, count, offp, buf, size); +} + +/* + * eeprom_attribute - EEPROM sysfs-node attributes + * + * NOTE Size will be changed in compliance with OF node. EEPROM attribute will + * be read-only as well if the corresponding flag is specified in OF node. + */ +static const BIN_ATTR_RW(eeprom, EEPROM_DEF_SIZE); + +/* + * csr_dbgfs_ops - CSR debugfs-node read/write operations + */ +static const struct file_operations csr_dbgfs_ops = { + .owner = THIS_MODULE, + .open = simple_open, + .write = idt_dbgfs_csr_write, + .read = idt_dbgfs_csr_read +}; + +/*=========================================================================== + * Driver init/deinit methods + *=========================================================================== + */ + +/* + * idt_set_defval() - disable EEPROM access by default + * @pdev: Pointer to the driver data + */ +static void idt_set_defval(struct idt_89hpesx_dev *pdev) +{ + /* If OF info is missing then use next values */ + pdev->eesize = 0; + pdev->eero = true; + pdev->inieecmd = 0; + pdev->eeaddr = 0; +} + +static const struct i2c_device_id ee_ids[]; + +/* + * idt_ee_match_id() - check whether the node belongs to compatible EEPROMs + */ +static const struct i2c_device_id *idt_ee_match_id(struct fwnode_handle *fwnode) +{ + const struct i2c_device_id *id = ee_ids; + const char *compatible, *p; + char devname[I2C_NAME_SIZE]; + int ret; + + ret = fwnode_property_read_string(fwnode, "compatible", &compatible); + if (ret) + return NULL; + + p = strchr(compatible, ','); + strscpy(devname, p ? p + 1 : compatible, sizeof(devname)); + /* Search through the device name */ + while (id->name[0]) { + if (strcmp(devname, id->name) == 0) + return id; + id++; + } + return NULL; +} + +/* + * idt_get_fw_data() - get IDT i2c-device parameters from device tree + * @pdev: Pointer to the driver data + */ +static void idt_get_fw_data(struct idt_89hpesx_dev *pdev) +{ + struct device *dev = &pdev->client->dev; + struct fwnode_handle *fwnode; + const struct i2c_device_id *ee_id = NULL; + u32 eeprom_addr; + int ret; + + device_for_each_child_node(dev, fwnode) { + ee_id = idt_ee_match_id(fwnode); + if (ee_id) + break; + + dev_warn(dev, "Skip unsupported EEPROM device %pfw\n", fwnode); + } + + /* If there is no fwnode EEPROM device, then set zero size */ + if (!ee_id) { + dev_warn(dev, "No fwnode, EEPROM access disabled"); + idt_set_defval(pdev); + return; + } + + /* Retrieve EEPROM size */ + pdev->eesize = (u32)ee_id->driver_data; + + /* Get custom EEPROM address from 'reg' attribute */ + ret = fwnode_property_read_u32(fwnode, "reg", &eeprom_addr); + if (ret || (eeprom_addr == 0)) { + dev_warn(dev, "No EEPROM reg found, use default address 0x%x", + EEPROM_DEF_ADDR); + pdev->inieecmd = 0; + pdev->eeaddr = EEPROM_DEF_ADDR << 1; + } else { + pdev->inieecmd = EEPROM_USA; + pdev->eeaddr = eeprom_addr << 1; + } + + /* Check EEPROM 'read-only' flag */ + if (fwnode_property_read_bool(fwnode, "read-only")) + pdev->eero = true; + else /* if (!fwnode_property_read_bool(node, "read-only")) */ + pdev->eero = false; + + fwnode_handle_put(fwnode); + dev_info(dev, "EEPROM of %d bytes found by 0x%x", + pdev->eesize, pdev->eeaddr); +} + +/* + * idt_create_pdev() - create and init data structure of the driver + * @client: i2c client of IDT PCIe-switch device + */ +static struct idt_89hpesx_dev *idt_create_pdev(struct i2c_client *client) +{ + struct idt_89hpesx_dev *pdev; + + /* Allocate memory for driver data */ + pdev = devm_kmalloc(&client->dev, sizeof(struct idt_89hpesx_dev), + GFP_KERNEL); + if (pdev == NULL) + return ERR_PTR(-ENOMEM); + + /* Initialize basic fields of the data */ + pdev->client = client; + i2c_set_clientdata(client, pdev); + + /* Read firmware nodes information */ + idt_get_fw_data(pdev); + + /* Initialize basic CSR CMD field - use full DWORD-sized r/w ops */ + pdev->inicsrcmd = CSR_DWE; + pdev->csr = CSR_DEF; + + /* Enable Packet Error Checking if it's supported by adapter */ + if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_PEC)) { + pdev->iniccode = CCODE_PEC; + client->flags |= I2C_CLIENT_PEC; + } else /* PEC is unsupported */ { + pdev->iniccode = 0; + } + + return pdev; +} + +/* + * idt_free_pdev() - free data structure of the driver + * @pdev: Pointer to the driver data + */ +static void idt_free_pdev(struct idt_89hpesx_dev *pdev) +{ + /* Clear driver data from device private field */ + i2c_set_clientdata(pdev->client, NULL); +} + +/* + * idt_set_smbus_ops() - set supported SMBus operations + * @pdev: Pointer to the driver data + * Return status of smbus check operations + */ +static int idt_set_smbus_ops(struct idt_89hpesx_dev *pdev) +{ + struct i2c_adapter *adapter = pdev->client->adapter; + struct device *dev = &pdev->client->dev; + + /* Check i2c adapter read functionality */ + if (i2c_check_functionality(adapter, + I2C_FUNC_SMBUS_READ_BLOCK_DATA)) { + pdev->smb_read = idt_smb_read_block; + dev_dbg(dev, "SMBus block-read op chosen"); + } else if (i2c_check_functionality(adapter, + I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { + pdev->smb_read = idt_smb_read_i2c_block; + dev_dbg(dev, "SMBus i2c-block-read op chosen"); + } else if (i2c_check_functionality(adapter, + I2C_FUNC_SMBUS_READ_WORD_DATA) && + i2c_check_functionality(adapter, + I2C_FUNC_SMBUS_READ_BYTE_DATA)) { + pdev->smb_read = idt_smb_read_word; + dev_warn(dev, "Use slow word/byte SMBus read ops"); + } else if (i2c_check_functionality(adapter, + I2C_FUNC_SMBUS_READ_BYTE_DATA)) { + pdev->smb_read = idt_smb_read_byte; + dev_warn(dev, "Use slow byte SMBus read op"); + } else /* no supported smbus read operations */ { + dev_err(dev, "No supported SMBus read op"); + return -EPFNOSUPPORT; + } + + /* Check i2c adapter write functionality */ + if (i2c_check_functionality(adapter, + I2C_FUNC_SMBUS_WRITE_BLOCK_DATA)) { + pdev->smb_write = idt_smb_write_block; + dev_dbg(dev, "SMBus block-write op chosen"); + } else if (i2c_check_functionality(adapter, + I2C_FUNC_SMBUS_WRITE_I2C_BLOCK)) { + pdev->smb_write = idt_smb_write_i2c_block; + dev_dbg(dev, "SMBus i2c-block-write op chosen"); + } else if (i2c_check_functionality(adapter, + I2C_FUNC_SMBUS_WRITE_WORD_DATA) && + i2c_check_functionality(adapter, + I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) { + pdev->smb_write = idt_smb_write_word; + dev_warn(dev, "Use slow word/byte SMBus write op"); + } else if (i2c_check_functionality(adapter, + I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) { + pdev->smb_write = idt_smb_write_byte; + dev_warn(dev, "Use slow byte SMBus write op"); + } else /* no supported smbus write operations */ { + dev_err(dev, "No supported SMBus write op"); + return -EPFNOSUPPORT; + } + + /* Initialize IDT SMBus slave interface mutex */ + mutex_init(&pdev->smb_mtx); + + return 0; +} + +/* + * idt_check_dev() - check whether it's really IDT 89HPESx device + * @pdev: Pointer to the driver data + * Return status of i2c adapter check operation + */ +static int idt_check_dev(struct idt_89hpesx_dev *pdev) +{ + struct device *dev = &pdev->client->dev; + u32 viddid; + int ret; + + /* Read VID and DID directly from IDT memory space */ + ret = idt_csr_read(pdev, IDT_VIDDID_CSR, &viddid); + if (ret != 0) { + dev_err(dev, "Failed to read VID/DID"); + return ret; + } + + /* Check whether it's IDT device */ + if ((viddid & IDT_VID_MASK) != PCI_VENDOR_ID_IDT) { + dev_err(dev, "Got unsupported VID/DID: 0x%08x", viddid); + return -ENODEV; + } + + dev_info(dev, "Found IDT 89HPES device VID:0x%04x, DID:0x%04x", + (viddid & IDT_VID_MASK), (viddid >> 16)); + + return 0; +} + +/* + * idt_create_sysfs_files() - create sysfs attribute files + * @pdev: Pointer to the driver data + * Return status of operation + */ +static int idt_create_sysfs_files(struct idt_89hpesx_dev *pdev) +{ + struct device *dev = &pdev->client->dev; + int ret; + + /* Don't do anything if EEPROM isn't accessible */ + if (pdev->eesize == 0) { + dev_dbg(dev, "Skip creating sysfs-files"); + return 0; + } + + /* + * Allocate memory for attribute file and copy the declared EEPROM attr + * structure to change some of fields + */ + pdev->ee_file = devm_kmemdup(dev, &bin_attr_eeprom, + sizeof(*pdev->ee_file), GFP_KERNEL); + if (!pdev->ee_file) + return -ENOMEM; + + /* In case of read-only EEPROM get rid of write ability */ + if (pdev->eero) { + pdev->ee_file->attr.mode &= ~0200; + pdev->ee_file->write = NULL; + } + /* Create EEPROM sysfs file */ + pdev->ee_file->size = pdev->eesize; + ret = sysfs_create_bin_file(&dev->kobj, pdev->ee_file); + if (ret != 0) { + dev_err(dev, "Failed to create EEPROM sysfs-node"); + return ret; + } + + return 0; +} + +/* + * idt_remove_sysfs_files() - remove sysfs attribute files + * @pdev: Pointer to the driver data + */ +static void idt_remove_sysfs_files(struct idt_89hpesx_dev *pdev) +{ + struct device *dev = &pdev->client->dev; + + /* Don't do anything if EEPROM wasn't accessible */ + if (pdev->eesize == 0) + return; + + /* Remove EEPROM sysfs file */ + sysfs_remove_bin_file(&dev->kobj, pdev->ee_file); +} + +/* + * idt_probe() - IDT 89HPESx driver probe() callback method + */ +static int idt_probe(struct i2c_client *client) +{ + struct idt_89hpesx_dev *pdev; + int ret; + + /* Create driver data */ + pdev = idt_create_pdev(client); + if (IS_ERR(pdev)) + return PTR_ERR(pdev); + + /* Set SMBus operations */ + ret = idt_set_smbus_ops(pdev); + if (ret != 0) + goto err_free_pdev; + + /* Check whether it is truly IDT 89HPESx device */ + ret = idt_check_dev(pdev); + if (ret != 0) + goto err_free_pdev; + + /* Create sysfs files */ + ret = idt_create_sysfs_files(pdev); + if (ret != 0) + goto err_free_pdev; + + /* Create debugfs files */ + debugfs_create_file(pdev->client->name, 0600, client->debugfs, pdev, &csr_dbgfs_ops); + + return 0; + +err_free_pdev: + idt_free_pdev(pdev); + + return ret; +} + +/* + * idt_remove() - IDT 89HPESx driver remove() callback method + */ +static void idt_remove(struct i2c_client *client) +{ + struct idt_89hpesx_dev *pdev = i2c_get_clientdata(client); + + /* Remove sysfs files */ + idt_remove_sysfs_files(pdev); + + /* Discard driver data structure */ + idt_free_pdev(pdev); +} + +/* + * ee_ids - array of supported EEPROMs + */ +static const struct i2c_device_id ee_ids[] = { + { "24c32", 4096}, + { "24c64", 8192}, + { "24c128", 16384}, + { "24c256", 32768}, + { "24c512", 65536}, + {} +}; +MODULE_DEVICE_TABLE(i2c, ee_ids); + +/* + * idt_ids - supported IDT 89HPESx devices + */ +static const struct i2c_device_id idt_ids[] = { + { "89hpes8nt2" }, + { "89hpes12nt3" }, + + { "89hpes24nt6ag2" }, + { "89hpes32nt8ag2" }, + { "89hpes32nt8bg2" }, + { "89hpes12nt12g2" }, + { "89hpes16nt16g2" }, + { "89hpes24nt24g2" }, + { "89hpes32nt24ag2" }, + { "89hpes32nt24bg2" }, + + { "89hpes12n3" }, + { "89hpes12n3a" }, + { "89hpes24n3" }, + { "89hpes24n3a" }, + + { "89hpes32h8" }, + { "89hpes32h8g2" }, + { "89hpes48h12" }, + { "89hpes48h12g2" }, + { "89hpes48h12ag2" }, + { "89hpes16h16" }, + { "89hpes22h16" }, + { "89hpes22h16g2" }, + { "89hpes34h16" }, + { "89hpes34h16g2" }, + { "89hpes64h16" }, + { "89hpes64h16g2" }, + { "89hpes64h16ag2" }, + + /* { "89hpes3t3" }, // No SMBus-slave iface */ + { "89hpes12t3g2" }, + { "89hpes24t3g2" }, + /* { "89hpes4t4" }, // No SMBus-slave iface */ + { "89hpes16t4" }, + { "89hpes4t4g2" }, + { "89hpes10t4g2" }, + { "89hpes16t4g2" }, + { "89hpes16t4ag2" }, + { "89hpes5t5" }, + { "89hpes6t5" }, + { "89hpes8t5" }, + { "89hpes8t5a" }, + { "89hpes24t6" }, + { "89hpes6t6g2" }, + { "89hpes24t6g2" }, + { "89hpes16t7" }, + { "89hpes32t8" }, + { "89hpes32t8g2" }, + { "89hpes48t12" }, + { "89hpes48t12g2" }, + { /* END OF LIST */ } +}; +MODULE_DEVICE_TABLE(i2c, idt_ids); + +static const struct of_device_id idt_of_match[] = { + { .compatible = "idt,89hpes8nt2", }, + { .compatible = "idt,89hpes12nt3", }, + + { .compatible = "idt,89hpes24nt6ag2", }, + { .compatible = "idt,89hpes32nt8ag2", }, + { .compatible = "idt,89hpes32nt8bg2", }, + { .compatible = "idt,89hpes12nt12g2", }, + { .compatible = "idt,89hpes16nt16g2", }, + { .compatible = "idt,89hpes24nt24g2", }, + { .compatible = "idt,89hpes32nt24ag2", }, + { .compatible = "idt,89hpes32nt24bg2", }, + + { .compatible = "idt,89hpes12n3", }, + { .compatible = "idt,89hpes12n3a", }, + { .compatible = "idt,89hpes24n3", }, + { .compatible = "idt,89hpes24n3a", }, + + { .compatible = "idt,89hpes32h8", }, + { .compatible = "idt,89hpes32h8g2", }, + { .compatible = "idt,89hpes48h12", }, + { .compatible = "idt,89hpes48h12g2", }, + { .compatible = "idt,89hpes48h12ag2", }, + { .compatible = "idt,89hpes16h16", }, + { .compatible = "idt,89hpes22h16", }, + { .compatible = "idt,89hpes22h16g2", }, + { .compatible = "idt,89hpes34h16", }, + { .compatible = "idt,89hpes34h16g2", }, + { .compatible = "idt,89hpes64h16", }, + { .compatible = "idt,89hpes64h16g2", }, + { .compatible = "idt,89hpes64h16ag2", }, + + { .compatible = "idt,89hpes12t3g2", }, + { .compatible = "idt,89hpes24t3g2", }, + + { .compatible = "idt,89hpes16t4", }, + { .compatible = "idt,89hpes4t4g2", }, + { .compatible = "idt,89hpes10t4g2", }, + { .compatible = "idt,89hpes16t4g2", }, + { .compatible = "idt,89hpes16t4ag2", }, + { .compatible = "idt,89hpes5t5", }, + { .compatible = "idt,89hpes6t5", }, + { .compatible = "idt,89hpes8t5", }, + { .compatible = "idt,89hpes8t5a", }, + { .compatible = "idt,89hpes24t6", }, + { .compatible = "idt,89hpes6t6g2", }, + { .compatible = "idt,89hpes24t6g2", }, + { .compatible = "idt,89hpes16t7", }, + { .compatible = "idt,89hpes32t8", }, + { .compatible = "idt,89hpes32t8g2", }, + { .compatible = "idt,89hpes48t12", }, + { .compatible = "idt,89hpes48t12g2", }, + { }, +}; +MODULE_DEVICE_TABLE(of, idt_of_match); + +/* + * idt_driver - IDT 89HPESx driver structure + */ +static struct i2c_driver idt_driver = { + .driver = { + .name = IDT_NAME, + .of_match_table = idt_of_match, + }, + .probe = idt_probe, + .remove = idt_remove, + .id_table = idt_ids, +}; +module_i2c_driver(idt_driver); diff --git a/drivers/misc/eeprom/m24lr.c b/drivers/misc/eeprom/m24lr.c new file mode 100644 index 000000000..7a9fd45a8 --- /dev/null +++ b/drivers/misc/eeprom/m24lr.c @@ -0,0 +1,606 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * m24lr.c - Sysfs control interface for ST M24LR series RFID/NFC chips + * + * Copyright (c) 2025 Abd-Alrhman Masalkhi + * + * This driver implements both the sysfs-based control interface and EEPROM + * access for STMicroelectronics M24LR series chips (e.g., M24LR04E-R). + * It provides access to control registers for features such as password + * authentication, memory protection, and device configuration. In addition, + * it manages read and write operations to the EEPROM region of the chip. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define M24LR_WRITE_TIMEOUT 25u +#define M24LR_READ_TIMEOUT (M24LR_WRITE_TIMEOUT) + +/** + * struct m24lr_chip - describes chip-specific sysfs layout + * @sss_len: the length of the sss region + * @page_size: chip-specific limit on the maximum number of bytes allowed + * in a single write operation. + * @eeprom_size: size of the EEPROM in byte + * + * Supports multiple M24LR chip variants (e.g., M24LRxx) by allowing each + * to define its own set of sysfs attributes, depending on its available + * registers and features. + */ +struct m24lr_chip { + unsigned int sss_len; + unsigned int page_size; + unsigned int eeprom_size; +}; + +/** + * struct m24lr - core driver data for M24LR chip control + * @uid: 64 bits unique identifier stored in the device + * @sss_len: the length of the sss region + * @page_size: chip-specific limit on the maximum number of bytes allowed + * in a single write operation. + * @eeprom_size: size of the EEPROM in byte + * @ctl_regmap: regmap interface for accessing the system parameter sector + * @eeprom_regmap: regmap interface for accessing the EEPROM + * @lock: mutex to synchronize operations to the device + * + * Central data structure holding the state and resources used by the + * M24LR device driver. + */ +struct m24lr { + u64 uid; + unsigned int sss_len; + unsigned int page_size; + unsigned int eeprom_size; + struct regmap *ctl_regmap; + struct regmap *eeprom_regmap; + struct mutex lock; /* synchronize operations to the device */ +}; + +static const struct regmap_range m24lr_ctl_vo_ranges[] = { + regmap_reg_range(0, 63), +}; + +static const struct regmap_access_table m24lr_ctl_vo_table = { + .yes_ranges = m24lr_ctl_vo_ranges, + .n_yes_ranges = ARRAY_SIZE(m24lr_ctl_vo_ranges), +}; + +static const struct regmap_config m24lr_ctl_regmap_conf = { + .name = "m24lr_ctl", + .reg_stride = 1, + .reg_bits = 16, + .val_bits = 8, + .disable_locking = false, + .cache_type = REGCACHE_RBTREE,/* Flat can't be used, there's huge gap */ + .volatile_table = &m24lr_ctl_vo_table, +}; + +/* Chip descriptor for M24LR04E-R variant */ +static const struct m24lr_chip m24lr04e_r_chip = { + .page_size = 4, + .eeprom_size = 512, + .sss_len = 4, +}; + +/* Chip descriptor for M24LR16E-R variant */ +static const struct m24lr_chip m24lr16e_r_chip = { + .page_size = 4, + .eeprom_size = 2048, + .sss_len = 16, +}; + +/* Chip descriptor for M24LR64E-R variant */ +static const struct m24lr_chip m24lr64e_r_chip = { + .page_size = 4, + .eeprom_size = 8192, + .sss_len = 64, +}; + +static const struct i2c_device_id m24lr_ids[] = { + { "m24lr04e-r", (kernel_ulong_t)&m24lr04e_r_chip}, + { "m24lr16e-r", (kernel_ulong_t)&m24lr16e_r_chip}, + { "m24lr64e-r", (kernel_ulong_t)&m24lr64e_r_chip}, + { } +}; +MODULE_DEVICE_TABLE(i2c, m24lr_ids); + +static const struct of_device_id m24lr_of_match[] = { + { .compatible = "st,m24lr04e-r", .data = &m24lr04e_r_chip}, + { .compatible = "st,m24lr16e-r", .data = &m24lr16e_r_chip}, + { .compatible = "st,m24lr64e-r", .data = &m24lr64e_r_chip}, + { } +}; +MODULE_DEVICE_TABLE(of, m24lr_of_match); + +/** + * m24lr_regmap_read - read data using regmap with retry on failure + * @regmap: regmap instance for the device + * @buf: buffer to store the read data + * @size: number of bytes to read + * @offset: starting register address + * + * Attempts to read a block of data from the device with retries and timeout. + * Some M24LR chips may transiently NACK reads (e.g., during internal write + * cycles), so this function retries with a short sleep until the timeout + * expires. + * + * Returns: + * Number of bytes read on success, + * -ETIMEDOUT if the read fails within the timeout window. + */ +static ssize_t m24lr_regmap_read(struct regmap *regmap, u8 *buf, + size_t size, unsigned int offset) +{ + int err; + unsigned long timeout, read_time; + ssize_t ret = -ETIMEDOUT; + + timeout = jiffies + msecs_to_jiffies(M24LR_READ_TIMEOUT); + do { + read_time = jiffies; + + err = regmap_bulk_read(regmap, offset, buf, size); + if (!err) { + ret = size; + break; + } + + usleep_range(1000, 2000); + } while (time_before(read_time, timeout)); + + return ret; +} + +/** + * m24lr_regmap_write - write data using regmap with retry on failure + * @regmap: regmap instance for the device + * @buf: buffer containing the data to write + * @size: number of bytes to write + * @offset: starting register address + * + * Attempts to write a block of data to the device with retries and a timeout. + * Some M24LR devices may NACK I2C writes while an internal write operation + * is in progress. This function retries the write operation with a short delay + * until it succeeds or the timeout is reached. + * + * Returns: + * Number of bytes written on success, + * -ETIMEDOUT if the write fails within the timeout window. + */ +static ssize_t m24lr_regmap_write(struct regmap *regmap, const u8 *buf, + size_t size, unsigned int offset) +{ + int err; + unsigned long timeout, write_time; + ssize_t ret = -ETIMEDOUT; + + timeout = jiffies + msecs_to_jiffies(M24LR_WRITE_TIMEOUT); + + do { + write_time = jiffies; + + err = regmap_bulk_write(regmap, offset, buf, size); + if (!err) { + ret = size; + break; + } + + usleep_range(1000, 2000); + } while (time_before(write_time, timeout)); + + return ret; +} + +static ssize_t m24lr_read(struct m24lr *m24lr, u8 *buf, size_t size, + unsigned int offset, bool is_eeprom) +{ + struct regmap *regmap; + ssize_t ret; + + if (is_eeprom) + regmap = m24lr->eeprom_regmap; + else + regmap = m24lr->ctl_regmap; + + mutex_lock(&m24lr->lock); + ret = m24lr_regmap_read(regmap, buf, size, offset); + mutex_unlock(&m24lr->lock); + + return ret; +} + +/** + * m24lr_write - write buffer to M24LR device with page alignment handling + * @m24lr: pointer to driver context + * @buf: data buffer to write + * @size: number of bytes to write + * @offset: target register address in the device + * @is_eeprom: true if the write should target the EEPROM, + * false if it should target the system parameters sector. + * + * Writes data to the M24LR device using regmap, split into chunks no larger + * than page_size to respect device-specific write limitations (e.g., page + * size or I2C hold-time concerns). Each chunk is aligned to the page boundary + * defined by page_size. + * + * Returns: + * Total number of bytes written on success, + * A negative error code if any write fails. + */ +static ssize_t m24lr_write(struct m24lr *m24lr, const u8 *buf, size_t size, + unsigned int offset, bool is_eeprom) +{ + unsigned int n, next_sector; + struct regmap *regmap; + ssize_t ret = 0; + ssize_t err; + + if (is_eeprom) + regmap = m24lr->eeprom_regmap; + else + regmap = m24lr->ctl_regmap; + + n = min_t(unsigned int, size, m24lr->page_size); + next_sector = roundup(offset + 1, m24lr->page_size); + if (offset + n > next_sector) + n = next_sector - offset; + + mutex_lock(&m24lr->lock); + while (n) { + err = m24lr_regmap_write(regmap, buf + offset, n, offset); + if (IS_ERR_VALUE(err)) { + if (!ret) + ret = err; + + break; + } + + offset += n; + size -= n; + ret += n; + n = min_t(unsigned int, size, m24lr->page_size); + } + mutex_unlock(&m24lr->lock); + + return ret; +} + +/** + * m24lr_write_pass - Write password to M24LR043-R using secure format + * @m24lr: Pointer to device control structure + * @buf: Input buffer containing hex-encoded password + * @count: Number of bytes in @buf + * @code: Operation code to embed between password copies + * + * This function parses a 4-byte password, encodes it in big-endian format, + * and constructs a 9-byte sequence of the form: + * + * [BE(password), code, BE(password)] + * + * The result is written to register 0x0900 (2304), which is the password + * register in M24LR04E-R chip. + * + * Return: Number of bytes written on success, or negative error code on failure + */ +static ssize_t m24lr_write_pass(struct m24lr *m24lr, const char *buf, + size_t count, u8 code) +{ + __be32 be_pass; + u8 output[9]; + ssize_t ret; + u32 pass; + int err; + + if (!count) + return -EINVAL; + + if (count > 8) + return -EINVAL; + + err = kstrtou32(buf, 16, &pass); + if (err) + return err; + + be_pass = cpu_to_be32(pass); + + memcpy(output, &be_pass, sizeof(be_pass)); + output[4] = code; + memcpy(output + 5, &be_pass, sizeof(be_pass)); + + mutex_lock(&m24lr->lock); + ret = m24lr_regmap_write(m24lr->ctl_regmap, output, 9, 2304); + mutex_unlock(&m24lr->lock); + + return ret; +} + +static ssize_t m24lr_read_reg_le(struct m24lr *m24lr, u64 *val, + unsigned int reg_addr, + unsigned int reg_size) +{ + ssize_t ret; + __le64 input = 0; + + ret = m24lr_read(m24lr, (u8 *)&input, reg_size, reg_addr, false); + if (IS_ERR_VALUE(ret)) + return ret; + + if (ret != reg_size) + return -EINVAL; + + switch (reg_size) { + case 1: + *val = *(u8 *)&input; + break; + case 2: + *val = le16_to_cpu((__le16)input); + break; + case 4: + *val = le32_to_cpu((__le32)input); + break; + case 8: + *val = le64_to_cpu((__le64)input); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int m24lr_nvmem_read(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + ssize_t err; + struct m24lr *m24lr = priv; + + if (!bytes) + return bytes; + + if (offset + bytes > m24lr->eeprom_size) + return -EINVAL; + + err = m24lr_read(m24lr, val, bytes, offset, true); + if (IS_ERR_VALUE(err)) + return err; + + return 0; +} + +static int m24lr_nvmem_write(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + ssize_t err; + struct m24lr *m24lr = priv; + + if (!bytes) + return -EINVAL; + + if (offset + bytes > m24lr->eeprom_size) + return -EINVAL; + + err = m24lr_write(m24lr, val, bytes, offset, true); + if (IS_ERR_VALUE(err)) + return err; + + return 0; +} + +static ssize_t m24lr_ctl_sss_read(struct file *filep, struct kobject *kobj, + const struct bin_attribute *attr, char *buf, + loff_t offset, size_t count) +{ + struct m24lr *m24lr = attr->private; + + if (!count) + return count; + + if (size_add(offset, count) > m24lr->sss_len) + return -EINVAL; + + return m24lr_read(m24lr, buf, count, offset, false); +} + +static ssize_t m24lr_ctl_sss_write(struct file *filep, struct kobject *kobj, + const struct bin_attribute *attr, char *buf, + loff_t offset, size_t count) +{ + struct m24lr *m24lr = attr->private; + + if (!count) + return -EINVAL; + + if (size_add(offset, count) > m24lr->sss_len) + return -EINVAL; + + return m24lr_write(m24lr, buf, count, offset, false); +} +static BIN_ATTR(sss, 0600, m24lr_ctl_sss_read, m24lr_ctl_sss_write, 0); + +static ssize_t new_pass_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct m24lr *m24lr = i2c_get_clientdata(to_i2c_client(dev)); + + return m24lr_write_pass(m24lr, buf, count, 7); +} +static DEVICE_ATTR_WO(new_pass); + +static ssize_t unlock_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct m24lr *m24lr = i2c_get_clientdata(to_i2c_client(dev)); + + return m24lr_write_pass(m24lr, buf, count, 9); +} +static DEVICE_ATTR_WO(unlock); + +static ssize_t uid_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct m24lr *m24lr = i2c_get_clientdata(to_i2c_client(dev)); + + return sysfs_emit(buf, "%llx\n", m24lr->uid); +} +static DEVICE_ATTR_RO(uid); + +static ssize_t total_sectors_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct m24lr *m24lr = i2c_get_clientdata(to_i2c_client(dev)); + + return sysfs_emit(buf, "%x\n", m24lr->sss_len); +} +static DEVICE_ATTR_RO(total_sectors); + +static struct attribute *m24lr_ctl_dev_attrs[] = { + &dev_attr_unlock.attr, + &dev_attr_new_pass.attr, + &dev_attr_uid.attr, + &dev_attr_total_sectors.attr, + NULL, +}; + +static const struct m24lr_chip *m24lr_get_chip(struct device *dev) +{ + const struct m24lr_chip *ret; + const struct i2c_device_id *id; + + id = i2c_match_id(m24lr_ids, to_i2c_client(dev)); + + if (dev->of_node && of_match_device(m24lr_of_match, dev)) + ret = of_device_get_match_data(dev); + else if (id) + ret = (void *)id->driver_data; + else + ret = acpi_device_get_match_data(dev); + + return ret; +} + +static int m24lr_probe(struct i2c_client *client) +{ + struct regmap_config eeprom_regmap_conf = {0}; + struct nvmem_config nvmem_conf = {0}; + struct device *dev = &client->dev; + struct i2c_client *eeprom_client; + const struct m24lr_chip *chip; + struct regmap *eeprom_regmap; + struct nvmem_device *nvmem; + struct regmap *ctl_regmap; + struct m24lr *m24lr; + u32 regs[2]; + long err; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -EOPNOTSUPP; + + chip = m24lr_get_chip(dev); + if (!chip) + return -ENODEV; + + m24lr = devm_kzalloc(dev, sizeof(struct m24lr), GFP_KERNEL); + if (!m24lr) + return -ENOMEM; + + err = device_property_read_u32_array(dev, "reg", regs, ARRAY_SIZE(regs)); + if (err) + return dev_err_probe(dev, err, "Failed to read 'reg' property\n"); + + /* Create a second I2C client for the eeprom interface */ + eeprom_client = devm_i2c_new_dummy_device(dev, client->adapter, regs[1]); + if (IS_ERR(eeprom_client)) + return dev_err_probe(dev, PTR_ERR(eeprom_client), + "Failed to create dummy I2C client for the EEPROM\n"); + + ctl_regmap = devm_regmap_init_i2c(client, &m24lr_ctl_regmap_conf); + if (IS_ERR(ctl_regmap)) + return dev_err_probe(dev, PTR_ERR(ctl_regmap), + "Failed to init regmap\n"); + + eeprom_regmap_conf.name = "m24lr_eeprom"; + eeprom_regmap_conf.reg_bits = 16; + eeprom_regmap_conf.val_bits = 8; + eeprom_regmap_conf.disable_locking = true; + eeprom_regmap_conf.max_register = chip->eeprom_size - 1; + + eeprom_regmap = devm_regmap_init_i2c(eeprom_client, + &eeprom_regmap_conf); + if (IS_ERR(eeprom_regmap)) + return dev_err_probe(dev, PTR_ERR(eeprom_regmap), + "Failed to init regmap\n"); + + mutex_init(&m24lr->lock); + m24lr->sss_len = chip->sss_len; + m24lr->page_size = chip->page_size; + m24lr->eeprom_size = chip->eeprom_size; + m24lr->eeprom_regmap = eeprom_regmap; + m24lr->ctl_regmap = ctl_regmap; + + nvmem_conf.dev = &eeprom_client->dev; + nvmem_conf.owner = THIS_MODULE; + nvmem_conf.type = NVMEM_TYPE_EEPROM; + nvmem_conf.reg_read = m24lr_nvmem_read; + nvmem_conf.reg_write = m24lr_nvmem_write; + nvmem_conf.size = chip->eeprom_size; + nvmem_conf.word_size = 1; + nvmem_conf.stride = 1; + nvmem_conf.priv = m24lr; + + nvmem = devm_nvmem_register(dev, &nvmem_conf); + if (IS_ERR(nvmem)) + return dev_err_probe(dev, PTR_ERR(nvmem), + "Failed to register nvmem\n"); + + i2c_set_clientdata(client, m24lr); + i2c_set_clientdata(eeprom_client, m24lr); + + bin_attr_sss.size = chip->sss_len; + bin_attr_sss.private = m24lr; + err = sysfs_create_bin_file(&dev->kobj, &bin_attr_sss); + if (err) + return dev_err_probe(dev, err, + "Failed to create sss bin file\n"); + + /* test by reading the uid, if success store it */ + err = m24lr_read_reg_le(m24lr, &m24lr->uid, 2324, sizeof(m24lr->uid)); + if (IS_ERR_VALUE(err)) + goto remove_bin_file; + + return 0; + +remove_bin_file: + sysfs_remove_bin_file(&dev->kobj, &bin_attr_sss); + + return err; +} + +static void m24lr_remove(struct i2c_client *client) +{ + sysfs_remove_bin_file(&client->dev.kobj, &bin_attr_sss); +} + +ATTRIBUTE_GROUPS(m24lr_ctl_dev); + +static struct i2c_driver m24lr_driver = { + .driver = { + .name = "m24lr", + .of_match_table = m24lr_of_match, + .dev_groups = m24lr_ctl_dev_groups, + }, + .probe = m24lr_probe, + .remove = m24lr_remove, + .id_table = m24lr_ids, +}; +module_i2c_driver(m24lr_driver); + +MODULE_AUTHOR("Abd-Alrhman Masalkhi"); +MODULE_DESCRIPTION("st m24lr control driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/eeprom/max6875.c b/drivers/misc/eeprom/max6875.c new file mode 100644 index 000000000..5731d2dc8 --- /dev/null +++ b/drivers/misc/eeprom/max6875.c @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * max6875.c - driver for MAX6874/MAX6875 + * + * Copyright (C) 2005 Ben Gardner + * + * Based on eeprom.c + * + * The MAX6875 has a bank of registers and two banks of EEPROM. + * Address ranges are defined as follows: + * * 0x0000 - 0x0046 = configuration registers + * * 0x8000 - 0x8046 = configuration EEPROM + * * 0x8100 - 0x82FF = user EEPROM + * + * This driver makes the user EEPROM available for read. + * + * The registers & config EEPROM should be accessed via i2c-dev. + * + * The MAX6875 ignores the lowest address bit, so each chip responds to + * two addresses - 0x50/0x51 and 0x52/0x53. + * + * Note that the MAX6875 uses i2c_smbus_write_byte_data() to set the read + * address, so this driver is destructive if loaded for the wrong EEPROM chip. + */ + +#include +#include +#include +#include +#include + +/* The MAX6875 can only read/write 16 bytes at a time */ +#define SLICE_SIZE 16 +#define SLICE_BITS 4 + +/* USER EEPROM is at addresses 0x8100 - 0x82FF */ +#define USER_EEPROM_BASE 0x8100 +#define USER_EEPROM_SIZE 0x0200 +#define USER_EEPROM_SLICES 32 + +/* MAX6875 commands */ +#define MAX6875_CMD_BLK_READ 0x84 + +/* Each client has this additional data */ +struct max6875_data { + struct i2c_client *fake_client; + struct mutex update_lock; + + u32 valid; + u8 data[USER_EEPROM_SIZE]; + unsigned long last_updated[USER_EEPROM_SLICES]; +}; + +static void max6875_update_slice(struct i2c_client *client, int slice) +{ + struct max6875_data *data = i2c_get_clientdata(client); + int i, j, addr; + u8 *buf; + + if (slice >= USER_EEPROM_SLICES) + return; + + mutex_lock(&data->update_lock); + + buf = &data->data[slice << SLICE_BITS]; + + if (!(data->valid & (1 << slice)) || + time_after(jiffies, data->last_updated[slice])) { + + dev_dbg(&client->dev, "Starting update of slice %u\n", slice); + + data->valid &= ~(1 << slice); + + addr = USER_EEPROM_BASE + (slice << SLICE_BITS); + + /* select the eeprom address */ + if (i2c_smbus_write_byte_data(client, addr >> 8, addr & 0xFF)) { + dev_err(&client->dev, "address set failed\n"); + goto exit_up; + } + + if (i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { + if (i2c_smbus_read_i2c_block_data(client, + MAX6875_CMD_BLK_READ, + SLICE_SIZE, + buf) != SLICE_SIZE) { + goto exit_up; + } + } else { + for (i = 0; i < SLICE_SIZE; i++) { + j = i2c_smbus_read_byte(client); + if (j < 0) { + goto exit_up; + } + buf[i] = j; + } + } + data->last_updated[slice] = jiffies; + data->valid |= (1 << slice); + } +exit_up: + mutex_unlock(&data->update_lock); +} + +static ssize_t max6875_read(struct file *filp, struct kobject *kobj, + const struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct i2c_client *client = kobj_to_i2c_client(kobj); + struct max6875_data *data = i2c_get_clientdata(client); + int slice, max_slice; + + /* refresh slices which contain requested bytes */ + max_slice = (off + count - 1) >> SLICE_BITS; + for (slice = (off >> SLICE_BITS); slice <= max_slice; slice++) + max6875_update_slice(client, slice); + + memcpy(buf, &data->data[off], count); + + return count; +} + +static const struct bin_attribute user_eeprom_attr = { + .attr = { + .name = "eeprom", + .mode = S_IRUGO, + }, + .size = USER_EEPROM_SIZE, + .read = max6875_read, +}; + +static int max6875_probe(struct i2c_client *client) +{ + struct i2c_adapter *adapter = client->adapter; + struct max6875_data *data; + int err; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WRITE_BYTE_DATA + | I2C_FUNC_SMBUS_READ_BYTE)) + return -ENODEV; + + /* Only bind to even addresses */ + if (client->addr & 1) + return -ENODEV; + + data = kzalloc_obj(struct max6875_data); + if (!data) + return -ENOMEM; + + /* A fake client is created on the odd address */ + data->fake_client = i2c_new_dummy_device(client->adapter, client->addr + 1); + if (IS_ERR(data->fake_client)) { + err = PTR_ERR(data->fake_client); + goto exit_kfree; + } + + /* Init real i2c_client */ + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + err = sysfs_create_bin_file(&client->dev.kobj, &user_eeprom_attr); + if (err) + goto exit_remove_fake; + + return 0; + +exit_remove_fake: + i2c_unregister_device(data->fake_client); +exit_kfree: + kfree(data); + return err; +} + +static void max6875_remove(struct i2c_client *client) +{ + struct max6875_data *data = i2c_get_clientdata(client); + + i2c_unregister_device(data->fake_client); + + sysfs_remove_bin_file(&client->dev.kobj, &user_eeprom_attr); + kfree(data); +} + +static const struct i2c_device_id max6875_id[] = { + { "max6875" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max6875_id); + +static struct i2c_driver max6875_driver = { + .driver = { + .name = "max6875", + }, + .probe = max6875_probe, + .remove = max6875_remove, + .id_table = max6875_id, +}; + +module_i2c_driver(max6875_driver); + +MODULE_AUTHOR("Ben Gardner "); +MODULE_DESCRIPTION("MAX6875 driver"); +MODULE_LICENSE("GPL"); -- cgit v1.3.1