| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606 |
- // 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");
|