From nobody Mon Oct 6 22:51:14 2025 Received: from mail-wr1-f51.google.com (mail-wr1-f51.google.com [209.85.221.51]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id F018621A420; Thu, 17 Jul 2025 06:40:13 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.51 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752734416; cv=none; b=Y59VrK9YfKkZCso5U2+pqXwbv9u1uX0WBxEHK4fqR0GNMLAgU2xZCHmBJIMR6z+OTu6+Acc2eYWz1b90zpRD+P8wO1c5TiliDmdsjSLrHMy5k4hBa/2P6CLQWfn+jCAq96ujIzv7VqdYLKJGncZMaQJJ16h8Axil5vyNXHjO6Sw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752734416; c=relaxed/simple; bh=6hA8ciBCd3WA12b0GjRrS+4ViHu4seUPNXKLFORzULA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Xyt2LJeM669hzPHNqOXeAF8t5uDTLXRhW9jBaYV8W92DrLzGZTXwpC9M0uggLlPWPiJwwacmB7QP0cAL8xefARNuxYoVqnuZoGQy2AGQsWnK+5v7GIBWLcyb/cquRztqSGjzHApEKZcb7Ai1wvQ9mPxeRpa4RVCVZTiUnx4mgjk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=Vr9FISoP; arc=none smtp.client-ip=209.85.221.51 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="Vr9FISoP" Received: by mail-wr1-f51.google.com with SMTP id ffacd0b85a97d-3a51481a598so271621f8f.3; Wed, 16 Jul 2025 23:40:13 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1752734412; x=1753339212; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=ItGtPYEbM3tSncir4sEU0Wm0MuS+fmwAMihYNQuc5t8=; b=Vr9FISoPRTeqb3TurmW6POTLRRmNJfI99NQ0atRpbZ44N5Hgpvqane90gzB5fbd1Ul UELlU/j/9gRc+A4pt4Ouek13TpSjwb+Bi9senZO5KIbTeEE7/MLb+/Q5q3s5P7Vv8p4C 8m0UaPCEGdELotf/8QlWT3mbwCL5V9mfzWp0Jac12gILpJuEVMwlNBMu6qrgJBzZyw6Z 6kcnG2hZlXqrJS3/zmqoJARCH1h4MeIRSQs7efUTIEGMau8g9Dv7zoMLpSggimH04XYB gU6jWhAPmmQFz8oHb9bUks0lYmgTr1siOTZj50+mOJWyB0R2Xn8dV2w3BVqZSo3SPkSm zfKQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1752734412; x=1753339212; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=ItGtPYEbM3tSncir4sEU0Wm0MuS+fmwAMihYNQuc5t8=; b=olmMiVAREslaC9NDpcr1PGN+/vPL9cXf5+7NeTF4HAbGo2V+/BV8EpsvsPHiMbvX5y ARlY5FrUbOmz+wqpMpWFfRFef/KcU4e09S9JkHGMu7dT7OKRH++FCpNWM8rv81GzpQ2p rcRNW7UxbvpxWLQkSPm6/Y5NDqsAt6pmUFTKmM1ZHL2SYPzLuLFjGcNDKtl5uDhV4bK/ 73nUg90mBGYVR4p7U+PDgrIxNh0xJ74cDcdmP8q64ofsPOCy+SDF+elRS7nIo/X8Ksb/ n0oj2M7Cg2wIhK18xWSuoSXHqdG1/W2U1loyCi37JKSOka/8yUH1jK2LA1+4szxhEJCS 5cLQ== X-Forwarded-Encrypted: i=1; AJvYcCUkfnsrGX54Xi554fH/s4aZMrccYN0BNSszGxZMmop0dwEUpu28PtNtKGz7gxhDzHpVTvMgv/82ilAE@vger.kernel.org, AJvYcCX+NWlWll7NhQL4MC9C/DJlxL7HOGgtNMrHxlTthPlS/MKlc6ujBGnckXcMGfkmeDanHsJOHCsbGgEH8hY3@vger.kernel.org X-Gm-Message-State: AOJu0YxVwhJJwwAmqB4vJVDWddeMp0TJs/JhjNrOOqRwlqQLpsXJyOKV gojEv+dIyInKNVpb2ct2W9QAWSfBr8n+ZJ/Mgg2RLYeHMuyFYMvsA7AD X-Gm-Gg: ASbGncti6z9crlMIzvlSus09Q431tfgVi8yvyIA0asuvJ8qv0B4jOZBKHGm+eO8tc1j nS5nAhD1PfjfcB4sBArl8u0GC57HhHa6tUd6Gq6x11q31a8GmCxylcGWvqUbqna5LkiBp/iqXSL bpWMsmzhiu3YV5pQn8XwcLajNkphI475fjQXkW4CmtkcX1KMkasjSRnA2s0EFTPCcuamtS0XaPp D0SUQXXWMbVobdmGCXK5nnpSL6Evx10o0L4dXoVJwknd/e7fXtlWuiA6YX+g7Tdzmp0H1dWzK8L eNtedAF7Mus/E9YmAUyHLOrSttuUai1fe12yKBUgfYCQo6lCN1sj+cySAPd4Z5WVnaGEBRnP2Ro po71wYQm5qz0sqB01wJgEih9mGoeCDd8= X-Google-Smtp-Source: AGHT+IEqXpza5j0mh0p3KSp/tmDjguZSbueWrZWr/rHUTzOdnkYpdNC8nidrVU1I1UyG+8q4w+iP8A== X-Received: by 2002:a05:6000:310c:b0:3a4:d4e5:498a with SMTP id ffacd0b85a97d-3b60e53ef49mr3991113f8f.42.1752734411769; Wed, 16 Jul 2025 23:40:11 -0700 (PDT) Received: from masalkhi.. ([61.8.129.68]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-3b5e8e1e135sm20039788f8f.72.2025.07.16.23.40.09 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 16 Jul 2025 23:40:11 -0700 (PDT) From: Abd-Alrhman Masalkhi To: arnd@arndb.de, gregkh@linuxfoundation.org, robh@kernel.org, krzk+dt@kernel.org Cc: conor+dt@kernel.org, luoyifan@cmss.chinamobile.com, linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, christophe.jaillet@wanadoo.fr, abd.masalkhi@gmail.com Subject: [PATCH v7 2/3] eeprom: add driver for ST M24LR series RFID/NFC EEPROM chips Date: Thu, 17 Jul 2025 06:39:33 +0000 Message-ID: <20250717063934.5083-3-abd.masalkhi@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20250717063934.5083-1-abd.masalkhi@gmail.com> References: <20250717063934.5083-1-abd.masalkhi@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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 --- Changes in v7: - Drop unused function: m24lr_parse_le_value() - Link to v6: https://lore.kernel.org/all/20250706105311.395162-3-abd.masa= lkhi@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.masal= khi@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.ma= salkhi@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.ma= salkhi@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.ma= salkhi@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.ma= salkhi@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. =20 +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) +=3D eeprom_93xx46.o obj-$(CONFIG_EEPROM_DIGSY_MTC_CFG) +=3D digsy_mtc_eeprom.o obj-$(CONFIG_EEPROM_IDT_89HPESX) +=3D idt_89hpesx.o obj-$(CONFIG_EEPROM_EE1004) +=3D ee1004.o +obj-$(CONFIG_EEPROM_M24LR) +=3D 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 + * + * 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 additio= n, + * it manages read and write operations to the EEPROM region of the chip. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define M24LR_WRITE_TIMEOUT 25u +#define M24LR_READ_TIMEOUT (M24LR_WRITE_TIMEOUT) + +/** + * struct m24lr_chip - describes chip-specific sysfs layout + * @sss_len: the length of the sss region + * @page_size: chip-specific limit on the maximum number of bytes allow= ed + * 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 allow= ed + * in a single write operation. + * @eeprom_size: size of the EEPROM in byte + * @ctl_regmap: regmap interface for accessing the system parameter sec= tor + * @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[] =3D { + regmap_reg_range(0, 63), +}; + +static const struct regmap_access_table m24lr_ctl_vo_table =3D { + .yes_ranges =3D m24lr_ctl_vo_ranges, + .n_yes_ranges =3D ARRAY_SIZE(m24lr_ctl_vo_ranges), +}; + +static const struct regmap_config m24lr_ctl_regmap_conf =3D { + .name =3D "m24lr_ctl", + .reg_stride =3D 1, + .reg_bits =3D 16, + .val_bits =3D 8, + .disable_locking =3D false, + .cache_type =3D REGCACHE_RBTREE,/* Flat can't be used, there's huge gap */ + .volatile_table =3D &m24lr_ctl_vo_table, +}; + +/* Chip descriptor for M24LR04E-R variant */ +static const struct m24lr_chip m24lr04e_r_chip =3D { + .page_size =3D 4, + .eeprom_size =3D 512, + .sss_len =3D 4, +}; + +/* Chip descriptor for M24LR16E-R variant */ +static const struct m24lr_chip m24lr16e_r_chip =3D { + .page_size =3D 4, + .eeprom_size =3D 2048, + .sss_len =3D 16, +}; + +/* Chip descriptor for M24LR64E-R variant */ +static const struct m24lr_chip m24lr64e_r_chip =3D { + .page_size =3D 4, + .eeprom_size =3D 8192, + .sss_len =3D 64, +}; + +static const struct i2c_device_id m24lr_ids[] =3D { + { "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[] =3D { + { .compatible =3D "st,m24lr04e-r", .data =3D &m24lr04e_r_chip}, + { .compatible =3D "st,m24lr16e-r", .data =3D &m24lr16e_r_chip}, + { .compatible =3D "st,m24lr64e-r", .data =3D &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 timeo= ut. + * 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 =3D -ETIMEDOUT; + + timeout =3D jiffies + msecs_to_jiffies(M24LR_READ_TIMEOUT); + do { + read_time =3D jiffies; + + err =3D regmap_bulk_read(regmap, offset, buf, size); + if (!err) { + ret =3D 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 time= out. + * 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 =3D -ETIMEDOUT; + + timeout =3D jiffies + msecs_to_jiffies(M24LR_WRITE_TIMEOUT); + + do { + write_time =3D jiffies; + + err =3D regmap_bulk_write(regmap, offset, buf, size); + if (!err) { + ret =3D 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 =3D m24lr->eeprom_regmap; + else + regmap =3D m24lr->ctl_regmap; + + mutex_lock(&m24lr->lock); + ret =3D 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 larg= er + * 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 boun= dary + * 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 =3D 0; + ssize_t err; + + if (is_eeprom) + regmap =3D m24lr->eeprom_regmap; + else + regmap =3D m24lr->ctl_regmap; + + n =3D min_t(unsigned int, size, m24lr->page_size); + next_sector =3D roundup(offset + 1, m24lr->page_size); + if (offset + n > next_sector) + n =3D next_sector - offset; + + mutex_lock(&m24lr->lock); + while (n) { + err =3D m24lr_regmap_write(regmap, buf + offset, n, offset); + if (IS_ERR_VALUE(err)) { + if (!ret) + ret =3D err; + + break; + } + + offset +=3D n; + size -=3D n; + ret +=3D n; + n =3D 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 forma= t, + * 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 f= ailure + */ +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 =3D kstrtou32(buf, 16, &pass); + if (err) + return err; + + be_pass =3D cpu_to_be32(pass); + + memcpy(output, &be_pass, sizeof(be_pass)); + output[4] =3D code; + memcpy(output + 5, &be_pass, sizeof(be_pass)); + + mutex_lock(&m24lr->lock); + ret =3D 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 =3D 0; + + ret =3D m24lr_read(m24lr, (u8 *)&input, reg_size, reg_addr, false); + if (IS_ERR_VALUE(ret)) + return ret; + + if (ret !=3D reg_size) + return -EINVAL; + + switch (reg_size) { + case 1: + *val =3D *(u8 *)&input; + break; + case 2: + *val =3D le16_to_cpu((__le16)input); + break; + case 4: + *val =3D le32_to_cpu((__le32)input); + break; + case 8: + *val =3D 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 =3D priv; + + if (!bytes) + return bytes; + + if (offset + bytes > m24lr->eeprom_size) + return -EINVAL; + + err =3D 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 =3D priv; + + if (!bytes) + return -EINVAL; + + if (offset + bytes > m24lr->eeprom_size) + return -EINVAL; + + err =3D 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 =3D 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 *kob= j, + const struct bin_attribute *attr, char *buf, + loff_t offset, size_t count) +{ + struct m24lr *m24lr =3D 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 =3D 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 *a= ttr, + const char *buf, size_t count) +{ + struct m24lr *m24lr =3D 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 =3D 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 =3D 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[] =3D { + &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 =3D i2c_match_id(m24lr_ids, to_i2c_client(dev)); + + if (dev->of_node && of_match_device(m24lr_of_match, dev)) + ret =3D of_device_get_match_data(dev); + else if (id) + ret =3D (void *)id->driver_data; + else + ret =3D acpi_device_get_match_data(dev); + + return ret; +} + +static int m24lr_probe(struct i2c_client *client) +{ + struct regmap_config eeprom_regmap_conf =3D {0}; + struct nvmem_config nvmem_conf =3D {0}; + struct device *dev =3D &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 =3D m24lr_get_chip(dev); + if (!chip) + return -ENODEV; + + m24lr =3D devm_kzalloc(dev, sizeof(struct m24lr), GFP_KERNEL); + if (!m24lr) + return -ENOMEM; + + err =3D 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 =3D 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 =3D 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 =3D "m24lr_eeprom"; + eeprom_regmap_conf.reg_bits =3D 16; + eeprom_regmap_conf.val_bits =3D 8; + eeprom_regmap_conf.disable_locking =3D true; + eeprom_regmap_conf.max_register =3D chip->eeprom_size - 1; + + eeprom_regmap =3D 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 =3D chip->sss_len; + m24lr->page_size =3D chip->page_size; + m24lr->eeprom_size =3D chip->eeprom_size; + m24lr->eeprom_regmap =3D eeprom_regmap; + m24lr->ctl_regmap =3D ctl_regmap; + + nvmem_conf.dev =3D &eeprom_client->dev; + nvmem_conf.owner =3D THIS_MODULE; + nvmem_conf.type =3D NVMEM_TYPE_EEPROM; + nvmem_conf.reg_read =3D m24lr_nvmem_read; + nvmem_conf.reg_write =3D m24lr_nvmem_write; + nvmem_conf.size =3D chip->eeprom_size; + nvmem_conf.word_size =3D 1; + nvmem_conf.stride =3D 1; + nvmem_conf.priv =3D m24lr; + + nvmem =3D 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 =3D chip->sss_len; + bin_attr_sss.private =3D m24lr; + err =3D 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 =3D 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 =3D { + .driver =3D { + .name =3D "m24lr", + .of_match_table =3D m24lr_of_match, + .dev_groups =3D m24lr_ctl_dev_groups, + }, + .probe =3D m24lr_probe, + .remove =3D m24lr_remove, + .id_table =3D m24lr_ids, +}; +module_i2c_driver(m24lr_driver); + +MODULE_AUTHOR("Abd-Alrhman Masalkhi"); +MODULE_DESCRIPTION("st m24lr control driver"); +MODULE_LICENSE("GPL"); --=20 2.43.0