adds support for STMicroelectronics M24LRxx devices, which expose
two separate I2C addresses: one for system control and one for EEPROM
access. The driver implements both a sysfs-based interface for control
registers (e.g. UID, password authentication) and an nvmem provider
for EEPROM access.
Signed-off-by: Abd-Alrhman Masalkhi <abd.masalkhi@gmail.com>
---
Changes in v7:
- Drop unused function: m24lr_parse_le_value()
- Link to v6: https://lore.kernel.org/all/20250706105311.395162-3-abd.masalkhi@gmail.com/
Changes in v6:
- Added cleanup on UID read failure (removes bin file before returning)
- Used size_add() to prevent overflow in sysfs read/write bounds check
- Corrected type of return variables (using ssize_t consistently)
- Replaced dev_err() with dev_err_probe()
- Small style and formatting cleanups
- Link to v5: https://lore.kernel.org/all/20250704123914.11216-3-abd.masalkhi@gmail.com/
Changes in v5:
- Fixed function signatures in m24lr_ctl_sss_read and m24lr_ctl_sss_write
to use const struct bin_attribute *attr
- Link to v4: https://lore.kernel.org/lkml/20250608182714.3359441-3-abd.masalkhi@gmail.com/
Changes in v4:
- Moved the source file to the eeprom/ directory
- Removed use of unlikely() macro
- Removed use of EIO as a fallback error
- Stopped dynamically creating sysfs attributes
- Replaced per-sector SSS attributes with a single bin_attribute
for all SSS
- Introduced total_sectors sysfs attribute to report the number
of valid sectors
- Avoided sharing a single show/store callback across multiple
attribute types
- Link to v3: https://lore.kernel.org/lkml/20250606120631.3140054-3-abd.masalkhi@gmail.com/
Changes in v3:
- Fully support the M24LR chips, including EEPROM access, no need for
the standard at24 driver to handle EEPROM separately.
- Rename the driver file from m24lr_ctl.c to m24lr.c.
- Rename all identifiers from the m24lr_ctl prefix to m24lr.
- Retain the m24lr_ctl prefix for control-related routines to distinguish
them from EEPROM-related logic.
- Drop usage of the I2C mux API.
- Use the NVMEM subsystem to handle EEPROM access.
- Add REGMAP support for EEPROM register access.
- Update Kconfig entry to reflect that the driver now supports both
control and EEPROM functionality.
- Link to v2: https://lore.kernel.org/lkml/20250601153022.2027919-3-abd.masalkhi@gmail.com/
Changes in v2:
- Fix compiling Errors and Warnings
- Replace scnprintf with sysfs_emit
- Drop success log message from probe.
- Link to v1: https://lore.kernel.org/lkml/20250531081159.2007319-3-abd.masalkhi@gmail.com/
Comment:
- Running checkpatch emit a warning for non-const regmap_config.
The variable must remain auto and mutable due to runtime manipulation.
---
drivers/misc/eeprom/Kconfig | 18 ++
drivers/misc/eeprom/Makefile | 1 +
drivers/misc/eeprom/m24lr.c | 606 +++++++++++++++++++++++++++++++++++
3 files changed, 625 insertions(+)
create mode 100644 drivers/misc/eeprom/m24lr.c
diff --git a/drivers/misc/eeprom/Kconfig b/drivers/misc/eeprom/Kconfig
index cb1c4b8e7fd3..cb0ce243babd 100644
--- a/drivers/misc/eeprom/Kconfig
+++ b/drivers/misc/eeprom/Kconfig
@@ -119,4 +119,22 @@ config EEPROM_EE1004
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
index 65794e526d5d..8f311fd6a4ce 100644
--- a/drivers/misc/eeprom/Makefile
+++ b/drivers/misc/eeprom/Makefile
@@ -7,3 +7,4 @@ 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/m24lr.c b/drivers/misc/eeprom/m24lr.c
new file mode 100644
index 000000000000..0e37f74881ce
--- /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 <abd.masalkhi@gmail.com>
+ *
+ * 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 <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/nvmem-provider.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/regmap.h>
+
+#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");
--
2.43.0
Hi all, Gentle ping. On Thu, Jul 17, 2025 at 06:39 +0000, Abd-Alrhman Masalkhi wrote: > adds support for STMicroelectronics M24LRxx devices, which expose > two separate I2C addresses: one for system control and one for EEPROM > access. The driver implements both a sysfs-based interface for control > registers (e.g. UID, password authentication) and an nvmem provider > for EEPROM access. > > Signed-off-by: Abd-Alrhman Masalkhi <abd.masalkhi@gmail.com> > --- > Changes in v7: > - Drop unused function: m24lr_parse_le_value() > - Link to v6: https://lore.kernel.org/all/20250706105311.395162-3-abd.masalkhi@gmail.com/ > > Changes in v6: > - Added cleanup on UID read failure (removes bin file before returning) > - Used size_add() to prevent overflow in sysfs read/write bounds check > - Corrected type of return variables (using ssize_t consistently) > - Replaced dev_err() with dev_err_probe() > - Small style and formatting cleanups > - Link to v5: https://lore.kernel.org/all/20250704123914.11216-3-abd.masalkhi@gmail.com/ > > Changes in v5: > - Fixed function signatures in m24lr_ctl_sss_read and m24lr_ctl_sss_write > to use const struct bin_attribute *attr > - Link to v4: https://lore.kernel.org/lkml/20250608182714.3359441-3-abd.masalkhi@gmail.com/ > > Changes in v4: > - Moved the source file to the eeprom/ directory > - Removed use of unlikely() macro > - Removed use of EIO as a fallback error > - Stopped dynamically creating sysfs attributes > - Replaced per-sector SSS attributes with a single bin_attribute > for all SSS > - Introduced total_sectors sysfs attribute to report the number > of valid sectors > - Avoided sharing a single show/store callback across multiple > attribute types > - Link to v3: https://lore.kernel.org/lkml/20250606120631.3140054-3-abd.masalkhi@gmail.com/ > > Changes in v3: > - Fully support the M24LR chips, including EEPROM access, no need for > the standard at24 driver to handle EEPROM separately. > - Rename the driver file from m24lr_ctl.c to m24lr.c. > - Rename all identifiers from the m24lr_ctl prefix to m24lr. > - Retain the m24lr_ctl prefix for control-related routines to distinguish > them from EEPROM-related logic. > - Drop usage of the I2C mux API. > - Use the NVMEM subsystem to handle EEPROM access. > - Add REGMAP support for EEPROM register access. > - Update Kconfig entry to reflect that the driver now supports both > control and EEPROM functionality. > - Link to v2: https://lore.kernel.org/lkml/20250601153022.2027919-3-abd.masalkhi@gmail.com/ > > Changes in v2: > - Fix compiling Errors and Warnings > - Replace scnprintf with sysfs_emit > - Drop success log message from probe. > - Link to v1: https://lore.kernel.org/lkml/20250531081159.2007319-3-abd.masalkhi@gmail.com/ > > Comment: > - Running checkpatch emit a warning for non-const regmap_config. > The variable must remain auto and mutable due to runtime manipulation. > --- > drivers/misc/eeprom/Kconfig | 18 ++ > drivers/misc/eeprom/Makefile | 1 + > drivers/misc/eeprom/m24lr.c | 606 +++++++++++++++++++++++++++++++++++ > 3 files changed, 625 insertions(+) > create mode 100644 drivers/misc/eeprom/m24lr.c > > diff --git a/drivers/misc/eeprom/Kconfig b/drivers/misc/eeprom/Kconfig > index cb1c4b8e7fd3..cb0ce243babd 100644 > --- a/drivers/misc/eeprom/Kconfig > +++ b/drivers/misc/eeprom/Kconfig > @@ -119,4 +119,22 @@ config EEPROM_EE1004 > 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 > index 65794e526d5d..8f311fd6a4ce 100644 > --- a/drivers/misc/eeprom/Makefile > +++ b/drivers/misc/eeprom/Makefile > @@ -7,3 +7,4 @@ 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/m24lr.c b/drivers/misc/eeprom/m24lr.c > new file mode 100644 > index 000000000000..0e37f74881ce > --- /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 <abd.masalkhi@gmail.com> > + * > + * 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 <linux/device.h> > +#include <linux/i2c.h> > +#include <linux/module.h> > +#include <linux/nvmem-provider.h> > +#include <linux/of.h> > +#include <linux/of_device.h> > +#include <linux/regmap.h> > + > +#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"); > -- > 2.43.0 > -- Best Regards, Abd-Alrhman
Hi all, Gentle ping. On Sat, Jul 26, 2025 at 10:31 +0000, Abd-Alrhman Masalkhi wrote: > Hi all, > > Gentle ping. > > On Thu, Jul 17, 2025 at 06:39 +0000, Abd-Alrhman Masalkhi wrote: >> adds support for STMicroelectronics M24LRxx devices, which expose >> two separate I2C addresses: one for system control and one for EEPROM >> access. The driver implements both a sysfs-based interface for control >> registers (e.g. UID, password authentication) and an nvmem provider >> for EEPROM access. >> >> Signed-off-by: Abd-Alrhman Masalkhi <abd.masalkhi@gmail.com> >> --- >> Changes in v7: >> - Drop unused function: m24lr_parse_le_value() >> - Link to v6: https://lore.kernel.org/all/20250706105311.395162-3-abd.masalkhi@gmail.com/ >> >> Changes in v6: >> - Added cleanup on UID read failure (removes bin file before returning) >> - Used size_add() to prevent overflow in sysfs read/write bounds check >> - Corrected type of return variables (using ssize_t consistently) >> - Replaced dev_err() with dev_err_probe() >> - Small style and formatting cleanups >> - Link to v5: https://lore.kernel.org/all/20250704123914.11216-3-abd.masalkhi@gmail.com/ >> >> Changes in v5: >> - Fixed function signatures in m24lr_ctl_sss_read and m24lr_ctl_sss_write >> to use const struct bin_attribute *attr >> - Link to v4: https://lore.kernel.org/lkml/20250608182714.3359441-3-abd.masalkhi@gmail.com/ >> >> Changes in v4: >> - Moved the source file to the eeprom/ directory >> - Removed use of unlikely() macro >> - Removed use of EIO as a fallback error >> - Stopped dynamically creating sysfs attributes >> - Replaced per-sector SSS attributes with a single bin_attribute >> for all SSS >> - Introduced total_sectors sysfs attribute to report the number >> of valid sectors >> - Avoided sharing a single show/store callback across multiple >> attribute types >> - Link to v3: https://lore.kernel.org/lkml/20250606120631.3140054-3-abd.masalkhi@gmail.com/ >> >> Changes in v3: >> - Fully support the M24LR chips, including EEPROM access, no need for >> the standard at24 driver to handle EEPROM separately. >> - Rename the driver file from m24lr_ctl.c to m24lr.c. >> - Rename all identifiers from the m24lr_ctl prefix to m24lr. >> - Retain the m24lr_ctl prefix for control-related routines to distinguish >> them from EEPROM-related logic. >> - Drop usage of the I2C mux API. >> - Use the NVMEM subsystem to handle EEPROM access. >> - Add REGMAP support for EEPROM register access. >> - Update Kconfig entry to reflect that the driver now supports both >> control and EEPROM functionality. >> - Link to v2: https://lore.kernel.org/lkml/20250601153022.2027919-3-abd.masalkhi@gmail.com/ >> >> Changes in v2: >> - Fix compiling Errors and Warnings >> - Replace scnprintf with sysfs_emit >> - Drop success log message from probe. >> - Link to v1: https://lore.kernel.org/lkml/20250531081159.2007319-3-abd.masalkhi@gmail.com/ >> >> Comment: >> - Running checkpatch emit a warning for non-const regmap_config. >> The variable must remain auto and mutable due to runtime manipulation. >> --- >> drivers/misc/eeprom/Kconfig | 18 ++ >> drivers/misc/eeprom/Makefile | 1 + >> drivers/misc/eeprom/m24lr.c | 606 +++++++++++++++++++++++++++++++++++ >> 3 files changed, 625 insertions(+) >> create mode 100644 drivers/misc/eeprom/m24lr.c >> >> diff --git a/drivers/misc/eeprom/Kconfig b/drivers/misc/eeprom/Kconfig >> index cb1c4b8e7fd3..cb0ce243babd 100644 >> --- a/drivers/misc/eeprom/Kconfig >> +++ b/drivers/misc/eeprom/Kconfig >> @@ -119,4 +119,22 @@ config EEPROM_EE1004 >> 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 >> index 65794e526d5d..8f311fd6a4ce 100644 >> --- a/drivers/misc/eeprom/Makefile >> +++ b/drivers/misc/eeprom/Makefile >> @@ -7,3 +7,4 @@ 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/m24lr.c b/drivers/misc/eeprom/m24lr.c >> new file mode 100644 >> index 000000000000..0e37f74881ce >> --- /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 <abd.masalkhi@gmail.com> >> + * >> + * 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 <linux/device.h> >> +#include <linux/i2c.h> >> +#include <linux/module.h> >> +#include <linux/nvmem-provider.h> >> +#include <linux/of.h> >> +#include <linux/of_device.h> >> +#include <linux/regmap.h> >> + >> +#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"); >> -- >> 2.43.0 >> > > -- > Best Regards, > Abd-Alrhman -- Best Regards, Abd-Alrhman
© 2016 - 2025 Red Hat, Inc.