eeprom: add driver for ST M24LR series RFID/NFC EEPROM chips
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> Link: https://lore.kernel.org/r/20250717063934.5083-3-abd.masalkhi@gmail.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
46b4ddd2c4
commit
cd5c5e0231
|
|
@ -120,4 +120,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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
Loading…
Reference in New Issue