The Maxim MAX77759 is a companion PMIC for USB Type-C applications and
includes Battery Charger, Fuel Gauge, temperature sensors, USB Type-C
Port Controller (TCPC), NVMEM, and a GPIO expander.

This driver exposes the non volatile memory using the platform device
registered by the core MFD driver.

Signed-off-by: André Draszik <andre.dras...@linaro.org>

---
v2:
* align sentinel in max77759_nvmem_of_id[] with other max77759 drivers
 (Christophe)
---
 MAINTAINERS                    |   1 +
 drivers/nvmem/Kconfig          |  12 ++++
 drivers/nvmem/Makefile         |   2 +
 drivers/nvmem/max77759-nvmem.c | 156 +++++++++++++++++++++++++++++++++++++++++
 4 files changed, 171 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 
ef3aadcf86ce35d8807733c94f790cde0f7255af..88c53e3fabe1760abf7914290c8729330739b0b9
 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14354,6 +14354,7 @@ S:      Maintained
 F:     Documentation/devicetree/bindings/*/maxim,max77759*.yaml
 F:     drivers/gpio/gpio-max77759.c
 F:     drivers/mfd/max77759.c
+F:     drivers/nvmem/max77759-nvmem.c
 F:     include/linux/mfd/max77759.h
 
 MAXIM MAX77802 PMIC REGULATOR DEVICE DRIVER
diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig
index 
8671b7c974b933e147154bb40b5d41b5730518d2..3de07ef524906ad24a89e58abdfe93529a83c80f
 100644
--- a/drivers/nvmem/Kconfig
+++ b/drivers/nvmem/Kconfig
@@ -154,6 +154,18 @@ config NVMEM_LPC18XX_OTP
          To compile this driver as a module, choose M here: the module
          will be called nvmem_lpc18xx_otp.
 
+config NVMEM_MAX77759
+       tristate "Maxim Integrated MAX77759 NVMEM Support"
+       depends on MFD_MAX77759
+       default MFD_MAX77759
+       help
+         Say Y here to include support for the user-accessible storage found
+         in Maxim Integrated MAX77759 PMICs. This IC provides space for 30
+         bytes of storage.
+
+         This driver can also be built as a module. If so, the module
+         will be called nvmem-max77759.
+
 config NVMEM_MESON_EFUSE
        tristate "Amlogic Meson GX eFuse Support"
        depends on (ARCH_MESON || COMPILE_TEST) && MESON_SM
diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile
index 
5b77bbb6488bf89bfb305750a1cbf4a6731a0a58..a9d03cfbbd27e68d40f8c330e72e20378b12a481
 100644
--- a/drivers/nvmem/Makefile
+++ b/drivers/nvmem/Makefile
@@ -34,6 +34,8 @@ obj-$(CONFIG_NVMEM_LPC18XX_EEPROM)    += 
nvmem_lpc18xx_eeprom.o
 nvmem_lpc18xx_eeprom-y                 := lpc18xx_eeprom.o
 obj-$(CONFIG_NVMEM_LPC18XX_OTP)                += nvmem_lpc18xx_otp.o
 nvmem_lpc18xx_otp-y                    := lpc18xx_otp.o
+obj-$(CONFIG_NVMEM_MAX77759)           += nvmem-max77759.o
+nvmem-max77759-y                       := max77759-nvmem.o
 obj-$(CONFIG_NVMEM_MESON_EFUSE)                += nvmem_meson_efuse.o
 nvmem_meson_efuse-y                    := meson-efuse.o
 obj-$(CONFIG_NVMEM_MESON_MX_EFUSE)     += nvmem_meson_mx_efuse.o
diff --git a/drivers/nvmem/max77759-nvmem.c b/drivers/nvmem/max77759-nvmem.c
new file mode 100644
index 
0000000000000000000000000000000000000000..bc535a73daaaf2caeb772cd17da61f8a030b6a6f
--- /dev/null
+++ b/drivers/nvmem/max77759-nvmem.c
@@ -0,0 +1,156 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright 2020 Google Inc
+// Copyright 2025 Linaro Ltd.
+//
+// NVMEM driver for Maxim MAX77759
+
+#include <linux/dev_printk.h>
+#include <linux/device.h>
+#include <linux/device/driver.h>
+#include <linux/err.h>
+#include <linux/mfd/max77759.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/nvmem-provider.h>
+#include <linux/overflow.h>
+#include <linux/platform_device.h>
+#include <linux/string.h>
+
+#define MAX77759_NVMEM_OPCODE_HEADER_LEN 3
+/*
+ * NVMEM commands have a three byte header (which becomes part of the command),
+ * so we need to subtract that.
+ */
+#define MAX77759_NVMEM_SIZE (MAX77759_MAXQ_OPCODE_MAXLENGTH \
+                            - MAX77759_NVMEM_OPCODE_HEADER_LEN)
+
+struct max77759_nvmem {
+       struct device *dev;
+       struct max77759_mfd *max77759_mfd;
+};
+
+static bool max77759_nvmem_is_valid(unsigned int offset, size_t bytes)
+{
+       return (offset + bytes - 1 <= MAX77759_NVMEM_SIZE);
+}
+
+static int max77759_nvmem_reg_read(void *priv, unsigned int offset,
+                                  void *val, size_t bytes)
+{
+       struct max77759_nvmem *nvmem = priv;
+       DEFINE_FLEX(struct max77759_maxq_command, cmd, cmd, length,
+                   MAX77759_NVMEM_OPCODE_HEADER_LEN);
+       DEFINE_FLEX(struct max77759_maxq_response, rsp, rsp, length,
+                   MAX77759_MAXQ_OPCODE_MAXLENGTH);
+       int ret;
+
+       if (!max77759_nvmem_is_valid(offset, bytes)) {
+               dev_err(nvmem->dev, "outside NVMEM area: %u / %zu\n",
+                       offset, bytes);
+               return -EINVAL;
+       }
+
+       cmd->cmd[0] = MAX77759_MAXQ_OPCODE_USER_SPACE_READ;
+       cmd->cmd[1] = offset;
+       cmd->cmd[2] = bytes;
+       rsp->length = bytes + MAX77759_NVMEM_OPCODE_HEADER_LEN;
+
+       ret = max77759_maxq_command(nvmem->max77759_mfd, cmd, rsp);
+       if (ret < 0)
+               return ret;
+
+       if (memcmp(cmd->cmd, rsp->rsp, MAX77759_NVMEM_OPCODE_HEADER_LEN)) {
+               dev_warn(nvmem->dev, "protocol error (read)\n");
+               return -EIO;
+       }
+
+       memcpy(val, &rsp->rsp[MAX77759_NVMEM_OPCODE_HEADER_LEN], bytes);
+
+       return 0;
+}
+
+static int max77759_nvmem_reg_write(void *priv, unsigned int offset,
+                                   void *val, size_t bytes)
+{
+       struct max77759_nvmem *nvmem = priv;
+       DEFINE_FLEX(struct max77759_maxq_command, cmd, cmd, length,
+                   MAX77759_MAXQ_OPCODE_MAXLENGTH);
+       DEFINE_FLEX(struct max77759_maxq_response, rsp, rsp, length,
+                   MAX77759_MAXQ_OPCODE_MAXLENGTH);
+       int ret;
+
+       if (!max77759_nvmem_is_valid(offset, bytes)) {
+               dev_err(nvmem->dev, "outside NVMEM area: %u / %zu\n",
+                       offset, bytes);
+               return -EINVAL;
+       }
+
+       cmd->cmd[0] = MAX77759_MAXQ_OPCODE_USER_SPACE_WRITE;
+       cmd->cmd[1] = offset;
+       cmd->cmd[2] = bytes;
+       memcpy(&cmd->cmd[MAX77759_NVMEM_OPCODE_HEADER_LEN], val, bytes);
+       cmd->length = bytes + MAX77759_NVMEM_OPCODE_HEADER_LEN;
+       rsp->length = cmd->length;
+
+       ret = max77759_maxq_command(nvmem->max77759_mfd, cmd, rsp);
+       if (ret < 0)
+               return ret;
+
+       if (memcmp(cmd->cmd, rsp->rsp, cmd->length)) {
+               dev_warn(nvmem->dev, "protocol error (write)\n");
+               return -EIO;
+       }
+
+       return 0;
+}
+
+static int max77759_nvmem_probe(struct platform_device *pdev)
+{
+       struct nvmem_config config = {
+               .dev = &pdev->dev,
+               .name = dev_name(&pdev->dev),
+               .id = NVMEM_DEVID_NONE,
+               .type = NVMEM_TYPE_EEPROM,
+               .ignore_wp = true,
+               .size = MAX77759_NVMEM_SIZE,
+               .word_size = sizeof(u8),
+               .stride = sizeof(u8),
+               .reg_read = max77759_nvmem_reg_read,
+               .reg_write = max77759_nvmem_reg_write,
+       };
+       struct max77759_nvmem *nvmem;
+
+       nvmem = devm_kzalloc(&pdev->dev, sizeof(*nvmem), GFP_KERNEL);
+       if (!nvmem)
+               return -ENOMEM;
+
+       nvmem->dev = &pdev->dev;
+       nvmem->max77759_mfd = dev_get_drvdata(pdev->dev.parent);
+
+       config.priv = nvmem;
+
+       return PTR_ERR_OR_ZERO(devm_nvmem_register(config.dev, &config));
+}
+
+static const struct of_device_id max77759_nvmem_of_id[] = {
+       { .compatible = "maxim,max77759-nvmem", },
+       { }
+};
+MODULE_DEVICE_TABLE(of, max77759_nvmem_of_id);
+
+static struct platform_driver max77759_nvmem_driver = {
+       .driver = {
+               .name = "max77759-nvmem",
+               .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+               .of_match_table = max77759_nvmem_of_id,
+       },
+       .probe = max77759_nvmem_probe,
+};
+
+module_platform_driver(max77759_nvmem_driver);
+
+MODULE_AUTHOR("André Draszik <andre.dras...@linaro.org>");
+MODULE_DESCRIPTION("NVMEM driver for Maxim MAX77759");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:max77759-nvmem");

-- 
2.49.0.rc0.332.g42c0ae87b1-goog


Reply via email to