// SPDX-License-Identifier: GPL-2.0-only // // Copyright 2020 Google Inc // Copyright 2025 Linaro Ltd. // // NVMEM driver for Maxim MAX77759 #include #include #include #include #include #include #include #include #include #include #include #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 "); MODULE_DESCRIPTION("NVMEM driver for Maxim MAX77759"); MODULE_LICENSE("GPL");