| // 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/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 *max77759; | |
| }; | |
| 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; | |
| 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, 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; | |
| 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, 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 = 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 const struct platform_device_id max77759_nvmem_platform_id[] = { | |
| { "max77759-nvmem", }, | |
| { } | |
| }; | |
| MODULE_DEVICE_TABLE(platform, max77759_nvmem_platform_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, | |
| .id_table = max77759_nvmem_platform_id, | |
| }; | |
| module_platform_driver(max77759_nvmem_driver); | |
| MODULE_AUTHOR("André Draszik <andre.draszik@linaro.org>"); | |
| MODULE_DESCRIPTION("NVMEM driver for Maxim MAX77759"); | |
| MODULE_LICENSE("GPL"); |