2020-05-29 13:10:36

by Alexandru Tachici

[permalink] [raw]
Subject: [PATCH v3 0/6] hwmon: pmbus: adm1266: add support

From: Alexandru Tachici <[email protected]>

Add PMBus probing driver for the adm1266 Cascadable
Super Sequencer with Margin Control and Fault Recording.
Driver is using the pmbus_core, creating sysfs files
under hwmon for inputs: vh1->vh4 and vp1->vp13.

1. Add PMBus probing driver for inputs vh1->vh4
and vp1->vp13.

2. Add Block WR command. A pmbus specific implementation
was required because block write with I2C_SMBUS_PROC_CALL
flag allows a maximum of 32 bytes to be received.

3. This makes adm1266 driver expose GPIOs
to user-space. Currently are read only. Future
developments on the firmware will allow
them to be writable.

4. Add debugfs files for go_command and read_state.

5. Blakcboxes are 64 bytes of chip state related data
that is generated on faults. Use the nvmem kernel api
to expose the blackbox chip functionality to userspace.

6. Device tree bindings for ADM1266.

Alexandru Tachici (6):
hwmon: pmbus: adm1266: add support
hwmon: (pmbus/core) Add Block WR
hwmon: pmbus: adm1266: Add support for GPIOs
hwmon: pmbus: adm1266: add debugfs attr for states
hwmon: pmbus: adm1266: read blackbox
dt-bindings: hwmon: Add bindings for ADM1266

Changelog v2 -> v3:
- added block write-read process call in pmbus-core
- expose GPIOs/PDIOs to user-space as readonly
- allow the user to issue commands to adm1266
in go_command debugfs file
- allow the user to read state of the adm1266 sequencer
from debugfs file read_state
- chip generated blackboxes can now be read
from the nvmem file

.../bindings/hwmon/adi,adm1266.yaml | 56 ++
Documentation/hwmon/adm1266.rst | 35 ++
drivers/hwmon/pmbus/Kconfig | 11 +-
drivers/hwmon/pmbus/Makefile | 1 +
drivers/hwmon/pmbus/adm1266.c | 500 ++++++++++++++++++
drivers/hwmon/pmbus/pmbus.h | 4 +
drivers/hwmon/pmbus/pmbus_core.c | 88 +++
7 files changed, 694 insertions(+), 1 deletion(-)
create mode 100644 Documentation/devicetree/bindings/hwmon/adi,adm1266.yaml
create mode 100644 Documentation/hwmon/adm1266.rst
create mode 100644 drivers/hwmon/pmbus/adm1266.c

--
2.20.1


2020-05-29 13:11:06

by Alexandru Tachici

[permalink] [raw]
Subject: [PATCH v3 4/6] hwmon: pmbus: adm1266: add debugfs attr for states

From: Alexandru Tachici <[email protected]>

Add debugfs files for go_command and read_state.

Signed-off-by: Alexandru Tachici <[email protected]>
---
drivers/hwmon/pmbus/adm1266.c | 47 +++++++++++++++++++++++++++++++++++
1 file changed, 47 insertions(+)

diff --git a/drivers/hwmon/pmbus/adm1266.c b/drivers/hwmon/pmbus/adm1266.c
index 190170300ef1..85d6795b79d3 100644
--- a/drivers/hwmon/pmbus/adm1266.c
+++ b/drivers/hwmon/pmbus/adm1266.c
@@ -19,6 +19,8 @@
#include "pmbus.h"

#define ADM1266_PDIO_CONFIG 0xD4
+#define ADM1266_GO_COMMAND 0xD8
+#define ADM1266_READ_STATE 0xD9
#define ADM1266_GPIO_CONFIG 0xE1
#define ADM1266_PDIO_STATUS 0xE9
#define ADM1266_GPIO_STATUS 0xEA
@@ -41,6 +43,7 @@ struct adm1266_data {
struct gpio_chip gc;
const char *gpio_names[ADM1266_GPIO_NR + ADM1266_PDIO_NR];
struct i2c_client *client;
+ struct dentry *debugfs_dir;
};

#if IS_ENABLED(CONFIG_GPIOLIB)
@@ -234,6 +237,48 @@ static inline int adm1266_config_gpio(struct adm1266_data *data)
}
#endif

+static int adm1266_get_state_op(void *pdata, u64 *state)
+{
+ struct adm1266_data *data = pdata;
+ int ret;
+
+ ret = i2c_smbus_read_word_data(data->client, ADM1266_READ_STATE);
+ if (ret < 0)
+ return ret;
+
+ *state = ret;
+
+ return 0;
+}
+
+static int adm1266_set_go_command_op(void *pdata, u64 val)
+{
+ struct adm1266_data *data = pdata;
+ u8 reg;
+
+ reg = FIELD_GET(GENMASK(4, 0), val);
+
+ return i2c_smbus_write_word_data(data->client, ADM1266_GO_COMMAND, reg);
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(go_command_fops, NULL, adm1266_set_go_command_op,
+ "%llu\n");
+DEFINE_DEBUGFS_ATTRIBUTE(read_state_fops, adm1266_get_state_op, NULL, "%llu\n");
+
+static void adm1266_debug_init(struct adm1266_data *data)
+{
+ struct dentry *root;
+ char dir_name[30];
+
+ sprintf(dir_name, "adm1266-%x_debugfs", data->client->addr);
+ root = debugfs_create_dir(dir_name, NULL);
+ data->debugfs_dir = root;
+ debugfs_create_file_unsafe("go_command", 0200, root, data,
+ &go_command_fops);
+ debugfs_create_file_unsafe("read_state", 0400, root, data,
+ &read_state_fops);
+}
+
static int adm1266_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
@@ -254,6 +299,8 @@ static int adm1266_probe(struct i2c_client *client,
if (ret < 0)
return ret;

+ adm1266_debug_init(data);
+
info = &data->info;
info->pages = 17;
info->format[PSC_VOLTAGE_OUT] = linear;
--
2.20.1

2020-05-29 13:11:21

by Alexandru Tachici

[permalink] [raw]
Subject: [PATCH v3 5/6] hwmon: pmbus: adm1266: read blackbox

From: Alexandru Tachici <[email protected]>

Use the nvmem kernel api to expose the black box
chip functionality to userspace.

Signed-off-by: Alexandru Tachici <[email protected]>
---
drivers/hwmon/pmbus/adm1266.c | 160 ++++++++++++++++++++++++++++++++++
1 file changed, 160 insertions(+)

diff --git a/drivers/hwmon/pmbus/adm1266.c b/drivers/hwmon/pmbus/adm1266.c
index 85d6795b79d3..831156004087 100644
--- a/drivers/hwmon/pmbus/adm1266.c
+++ b/drivers/hwmon/pmbus/adm1266.c
@@ -14,14 +14,19 @@
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/nvmem-provider.h>
#include <linux/slab.h>

#include "pmbus.h"

+#define ADM1266_BLACKBOX_CONFIG 0xD3
#define ADM1266_PDIO_CONFIG 0xD4
#define ADM1266_GO_COMMAND 0xD8
#define ADM1266_READ_STATE 0xD9
+#define ADM1266_READ_BLACKBOX 0xDE
#define ADM1266_GPIO_CONFIG 0xE1
+#define ADM1266_BLACKBOX_INFO 0xE6
#define ADM1266_PDIO_STATUS 0xE9
#define ADM1266_GPIO_STATUS 0xEA

@@ -38,12 +43,26 @@
#define ADM1266_PDIO_GLITCH_FILT(x) FIELD_GET(GENMASK(12, 9), x)
#define ADM1266_PDIO_OUT_CFG(x) FIELD_GET(GENMASK(2, 0), x)

+#define ADM1266_BLACKBOX_OFFSET 0x7F700
+#define ADM1266_BLACKBOX_SIZE 64
+
struct adm1266_data {
struct pmbus_driver_info info;
struct gpio_chip gc;
const char *gpio_names[ADM1266_GPIO_NR + ADM1266_PDIO_NR];
struct i2c_client *client;
struct dentry *debugfs_dir;
+ struct nvmem_config nvmem_config;
+ struct nvmem_device *nvmem;
+ u8 *dev_mem;
+};
+
+static const struct nvmem_cell_info adm1266_nvmem_cells[] = {
+ {
+ .name = "blackbox",
+ .offset = ADM1266_BLACKBOX_OFFSET,
+ .bytes = 2048,
+ },
};

#if IS_ENABLED(CONFIG_GPIOLIB)
@@ -261,6 +280,28 @@ static int adm1266_set_go_command_op(void *pdata, u64 val)
return i2c_smbus_write_word_data(data->client, ADM1266_GO_COMMAND, reg);
}

+static int adm1266_blackbox_information_read(struct seq_file *s, void *pdata)
+{
+ struct device *dev = s->private;
+ struct i2c_client *client = to_i2c_client(dev);
+ u8 read_buf[PMBUS_BLOCK_MAX + 1];
+ unsigned int latest_id;
+ int ret;
+
+ ret = i2c_smbus_read_block_data(client, ADM1266_BLACKBOX_INFO,
+ read_buf);
+ if (ret < 0)
+ return ret;
+
+ seq_puts(s, "BLACKBOX_INFORMATION:\n");
+ latest_id = read_buf[0] + (read_buf[1] << 8);
+ seq_printf(s, "Black box ID: %x\n", latest_id);
+ seq_printf(s, "Logic index: %x\n", read_buf[2]);
+ seq_printf(s, "Record count: %x\n", read_buf[3]);
+
+ return 0;
+}
+
DEFINE_DEBUGFS_ATTRIBUTE(go_command_fops, NULL, adm1266_set_go_command_op,
"%llu\n");
DEFINE_DEBUGFS_ATTRIBUTE(read_state_fops, adm1266_get_state_op, NULL, "%llu\n");
@@ -277,6 +318,121 @@ static void adm1266_debug_init(struct adm1266_data *data)
&go_command_fops);
debugfs_create_file_unsafe("read_state", 0400, root, data,
&read_state_fops);
+ debugfs_create_devm_seqfile(&data->client->dev, "blackbox_information",
+ root, adm1266_blackbox_information_read);
+}
+
+static int adm1266_nvmem_read_blackbox(struct adm1266_data *data, u8 *buf)
+{
+ u8 write_buf[PMBUS_BLOCK_MAX + 1];
+ u8 read_buf[PMBUS_BLOCK_MAX + 1];
+ int record_count;
+ int ret;
+ int i;
+
+ ret = i2c_smbus_read_block_data(data->client, ADM1266_BLACKBOX_INFO,
+ read_buf);
+ if (ret < 0)
+ return ret;
+
+ record_count = read_buf[3];
+
+ for (i = 0; i < record_count; i++) {
+ write_buf[0] = i;
+ ret = pmbus_block_wr(data->client, ADM1266_READ_BLACKBOX, 1,
+ write_buf, buf);
+ if (ret < 0)
+ return ret;
+
+ buf += ADM1266_BLACKBOX_SIZE;
+ }
+
+ return 0;
+}
+
+static bool adm1266_cell_is_accessed(const struct nvmem_cell_info *mem_cell,
+ unsigned int offset, size_t bytes)
+{
+ unsigned int start_addr = offset;
+ unsigned int end_addr = offset + bytes;
+ unsigned int cell_start = mem_cell->offset;
+ unsigned int cell_end = mem_cell->offset + mem_cell->bytes;
+
+ if (start_addr <= cell_end && cell_start <= end_addr)
+ return true;
+
+ return false;
+}
+
+static int adm1266_read_mem_cell(struct adm1266_data *data,
+ const struct nvmem_cell_info *mem_cell)
+{
+ u8 *mem_offset;
+ int ret;
+
+ switch (mem_cell->offset) {
+ case ADM1266_BLACKBOX_OFFSET:
+ mem_offset = data->dev_mem + mem_cell->offset;
+ ret = adm1266_nvmem_read_blackbox(data, mem_offset);
+ if (ret)
+ dev_err(&data->client->dev, "Could not read blackbox!");
+ return ret;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adm1266_nvmem_read(void *priv, unsigned int offset, void *val,
+ size_t bytes)
+{
+ const struct nvmem_cell_info *mem_cell;
+ struct adm1266_data *data = priv;
+ int ret;
+ int i;
+
+ for (i = 0; i < data->nvmem_config.ncells; i++) {
+ mem_cell = &adm1266_nvmem_cells[i];
+ if (!adm1266_cell_is_accessed(mem_cell, offset, bytes))
+ continue;
+
+ ret = adm1266_read_mem_cell(data, mem_cell);
+ if (ret < 0)
+ return ret;
+ }
+
+ memcpy(val, data->dev_mem + offset, bytes);
+
+ return 0;
+}
+
+static int adm1266_config_nvmem(struct adm1266_data *data)
+{
+ data->nvmem_config.name = dev_name(&data->client->dev);
+ data->nvmem_config.dev = &data->client->dev;
+ data->nvmem_config.root_only = true;
+ data->nvmem_config.read_only = true;
+ data->nvmem_config.owner = THIS_MODULE;
+ data->nvmem_config.reg_read = adm1266_nvmem_read;
+ data->nvmem_config.cells = adm1266_nvmem_cells;
+ data->nvmem_config.ncells = ARRAY_SIZE(adm1266_nvmem_cells);
+ data->nvmem_config.priv = data;
+ data->nvmem_config.stride = 1;
+ data->nvmem_config.word_size = 1;
+ data->nvmem_config.size = 0x80000;
+
+ data->nvmem = nvmem_register(&data->nvmem_config);
+ if (IS_ERR(data->nvmem)) {
+ dev_err(&data->client->dev, "Could not register nvmem!");
+ return PTR_ERR(data->nvmem);
+ }
+
+ data->dev_mem = devm_kzalloc(&data->client->dev,
+ data->nvmem_config.size,
+ GFP_KERNEL);
+ if (!data->dev_mem)
+ return -ENOMEM;
+
+ return 0;
}

static int adm1266_probe(struct i2c_client *client,
@@ -299,6 +455,10 @@ static int adm1266_probe(struct i2c_client *client,
if (ret < 0)
return ret;

+ ret = adm1266_config_nvmem(data);
+ if (ret < 0)
+ return ret;
+
adm1266_debug_init(data);

info = &data->info;
--
2.20.1

2020-05-29 18:45:30

by Guenter Roeck

[permalink] [raw]
Subject: Re: [PATCH v3 4/6] hwmon: pmbus: adm1266: add debugfs attr for states

On 5/29/20 6:05 AM, [email protected] wrote:
> From: Alexandru Tachici <[email protected]>
>
> Add debugfs files for go_command and read_state.
>
> Signed-off-by: Alexandru Tachici <[email protected]>
> ---
> drivers/hwmon/pmbus/adm1266.c | 47 +++++++++++++++++++++++++++++++++++
> 1 file changed, 47 insertions(+)
>
> diff --git a/drivers/hwmon/pmbus/adm1266.c b/drivers/hwmon/pmbus/adm1266.c
> index 190170300ef1..85d6795b79d3 100644
> --- a/drivers/hwmon/pmbus/adm1266.c
> +++ b/drivers/hwmon/pmbus/adm1266.c
> @@ -19,6 +19,8 @@
> #include "pmbus.h"
>
> #define ADM1266_PDIO_CONFIG 0xD4
> +#define ADM1266_GO_COMMAND 0xD8
> +#define ADM1266_READ_STATE 0xD9
> #define ADM1266_GPIO_CONFIG 0xE1
> #define ADM1266_PDIO_STATUS 0xE9
> #define ADM1266_GPIO_STATUS 0xEA
> @@ -41,6 +43,7 @@ struct adm1266_data {
> struct gpio_chip gc;
> const char *gpio_names[ADM1266_GPIO_NR + ADM1266_PDIO_NR];
> struct i2c_client *client;
> + struct dentry *debugfs_dir;
> };
>
> #if IS_ENABLED(CONFIG_GPIOLIB)
> @@ -234,6 +237,48 @@ static inline int adm1266_config_gpio(struct adm1266_data *data)
> }
> #endif
>
> +static int adm1266_get_state_op(void *pdata, u64 *state)
> +{
> + struct adm1266_data *data = pdata;
> + int ret;
> +
> + ret = i2c_smbus_read_word_data(data->client, ADM1266_READ_STATE);
> + if (ret < 0)
> + return ret;
> +
> + *state = ret;
> +
> + return 0;
> +}
> +
> +static int adm1266_set_go_command_op(void *pdata, u64 val)
> +{
> + struct adm1266_data *data = pdata;
> + u8 reg;
> +
> + reg = FIELD_GET(GENMASK(4, 0), val);
> +
> + return i2c_smbus_write_word_data(data->client, ADM1266_GO_COMMAND, reg);
> +}
> +
> +DEFINE_DEBUGFS_ATTRIBUTE(go_command_fops, NULL, adm1266_set_go_command_op,
> + "%llu\n");
> +DEFINE_DEBUGFS_ATTRIBUTE(read_state_fops, adm1266_get_state_op, NULL, "%llu\n");
> +
> +static void adm1266_debug_init(struct adm1266_data *data)
> +{
> + struct dentry *root;
> + char dir_name[30];
> +
> + sprintf(dir_name, "adm1266-%x_debugfs", data->client->addr);
> + root = debugfs_create_dir(dir_name, NULL);
> + data->debugfs_dir = root;
> + debugfs_create_file_unsafe("go_command", 0200, root, data,
> + &go_command_fops);

I am not entirely sure what this does, but from the description in the datasheet
it is way too critical to support as debugfs command. Anyone believing this
is needed should use ioctl commands instead.


> + debugfs_create_file_unsafe("read_state", 0400, root, data,
> + &read_state_fops);
> +}
> +

We have standard pmbus debug functions. Please use it.

> static int adm1266_probe(struct i2c_client *client,
> const struct i2c_device_id *id)
> {
> @@ -254,6 +299,8 @@ static int adm1266_probe(struct i2c_client *client,
> if (ret < 0)
> return ret;
>
> + adm1266_debug_init(data);
> +
> info = &data->info;
> info->pages = 17;
> info->format[PSC_VOLTAGE_OUT] = linear;
>

2020-05-29 18:47:25

by Guenter Roeck

[permalink] [raw]
Subject: Re: [PATCH v3 5/6] hwmon: pmbus: adm1266: read blackbox

On 5/29/20 6:05 AM, [email protected] wrote:
> From: Alexandru Tachici <[email protected]>
>
> Use the nvmem kernel api to expose the black box
> chip functionality to userspace.
>

This needs to be split into two functions: Add nvmem support, add
debugfs file.

Guenter

> Signed-off-by: Alexandru Tachici <[email protected]>
> ---
> drivers/hwmon/pmbus/adm1266.c | 160 ++++++++++++++++++++++++++++++++++
> 1 file changed, 160 insertions(+)
>
> diff --git a/drivers/hwmon/pmbus/adm1266.c b/drivers/hwmon/pmbus/adm1266.c
> index 85d6795b79d3..831156004087 100644
> --- a/drivers/hwmon/pmbus/adm1266.c
> +++ b/drivers/hwmon/pmbus/adm1266.c
> @@ -14,14 +14,19 @@
> #include <linux/init.h>
> #include <linux/kernel.h>
> #include <linux/module.h>
> +#include <linux/nvmem-consumer.h>
> +#include <linux/nvmem-provider.h>
> #include <linux/slab.h>
>
> #include "pmbus.h"
>
> +#define ADM1266_BLACKBOX_CONFIG 0xD3
> #define ADM1266_PDIO_CONFIG 0xD4
> #define ADM1266_GO_COMMAND 0xD8
> #define ADM1266_READ_STATE 0xD9
> +#define ADM1266_READ_BLACKBOX 0xDE
> #define ADM1266_GPIO_CONFIG 0xE1
> +#define ADM1266_BLACKBOX_INFO 0xE6
> #define ADM1266_PDIO_STATUS 0xE9
> #define ADM1266_GPIO_STATUS 0xEA
>
> @@ -38,12 +43,26 @@
> #define ADM1266_PDIO_GLITCH_FILT(x) FIELD_GET(GENMASK(12, 9), x)
> #define ADM1266_PDIO_OUT_CFG(x) FIELD_GET(GENMASK(2, 0), x)
>
> +#define ADM1266_BLACKBOX_OFFSET 0x7F700
> +#define ADM1266_BLACKBOX_SIZE 64
> +
> struct adm1266_data {
> struct pmbus_driver_info info;
> struct gpio_chip gc;
> const char *gpio_names[ADM1266_GPIO_NR + ADM1266_PDIO_NR];
> struct i2c_client *client;
> struct dentry *debugfs_dir;
> + struct nvmem_config nvmem_config;
> + struct nvmem_device *nvmem;
> + u8 *dev_mem;
> +};
> +
> +static const struct nvmem_cell_info adm1266_nvmem_cells[] = {
> + {
> + .name = "blackbox",
> + .offset = ADM1266_BLACKBOX_OFFSET,
> + .bytes = 2048,
> + },
> };
>
> #if IS_ENABLED(CONFIG_GPIOLIB)
> @@ -261,6 +280,28 @@ static int adm1266_set_go_command_op(void *pdata, u64 val)
> return i2c_smbus_write_word_data(data->client, ADM1266_GO_COMMAND, reg);
> }
>
> +static int adm1266_blackbox_information_read(struct seq_file *s, void *pdata)
> +{
> + struct device *dev = s->private;
> + struct i2c_client *client = to_i2c_client(dev);
> + u8 read_buf[PMBUS_BLOCK_MAX + 1];
> + unsigned int latest_id;
> + int ret;
> +
> + ret = i2c_smbus_read_block_data(client, ADM1266_BLACKBOX_INFO,
> + read_buf);
> + if (ret < 0)
> + return ret;
> +
> + seq_puts(s, "BLACKBOX_INFORMATION:\n");
> + latest_id = read_buf[0] + (read_buf[1] << 8);
> + seq_printf(s, "Black box ID: %x\n", latest_id);
> + seq_printf(s, "Logic index: %x\n", read_buf[2]);
> + seq_printf(s, "Record count: %x\n", read_buf[3]);
> +
> + return 0;
> +}
> +
> DEFINE_DEBUGFS_ATTRIBUTE(go_command_fops, NULL, adm1266_set_go_command_op,
> "%llu\n");
> DEFINE_DEBUGFS_ATTRIBUTE(read_state_fops, adm1266_get_state_op, NULL, "%llu\n");
> @@ -277,6 +318,121 @@ static void adm1266_debug_init(struct adm1266_data *data)
> &go_command_fops);
> debugfs_create_file_unsafe("read_state", 0400, root, data,
> &read_state_fops);
> + debugfs_create_devm_seqfile(&data->client->dev, "blackbox_information",
> + root, adm1266_blackbox_information_read);
> +}
> +
> +static int adm1266_nvmem_read_blackbox(struct adm1266_data *data, u8 *buf)
> +{
> + u8 write_buf[PMBUS_BLOCK_MAX + 1];
> + u8 read_buf[PMBUS_BLOCK_MAX + 1];
> + int record_count;
> + int ret;
> + int i;
> +
> + ret = i2c_smbus_read_block_data(data->client, ADM1266_BLACKBOX_INFO,
> + read_buf);
> + if (ret < 0)
> + return ret;
> +
> + record_count = read_buf[3];
> +
> + for (i = 0; i < record_count; i++) {
> + write_buf[0] = i;
> + ret = pmbus_block_wr(data->client, ADM1266_READ_BLACKBOX, 1,
> + write_buf, buf);
> + if (ret < 0)
> + return ret;
> +
> + buf += ADM1266_BLACKBOX_SIZE;
> + }
> +
> + return 0;
> +}
> +
> +static bool adm1266_cell_is_accessed(const struct nvmem_cell_info *mem_cell,
> + unsigned int offset, size_t bytes)
> +{
> + unsigned int start_addr = offset;
> + unsigned int end_addr = offset + bytes;
> + unsigned int cell_start = mem_cell->offset;
> + unsigned int cell_end = mem_cell->offset + mem_cell->bytes;
> +
> + if (start_addr <= cell_end && cell_start <= end_addr)
> + return true;
> +
> + return false;
> +}
> +
> +static int adm1266_read_mem_cell(struct adm1266_data *data,
> + const struct nvmem_cell_info *mem_cell)
> +{
> + u8 *mem_offset;
> + int ret;
> +
> + switch (mem_cell->offset) {
> + case ADM1266_BLACKBOX_OFFSET:
> + mem_offset = data->dev_mem + mem_cell->offset;
> + ret = adm1266_nvmem_read_blackbox(data, mem_offset);
> + if (ret)
> + dev_err(&data->client->dev, "Could not read blackbox!");
> + return ret;
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int adm1266_nvmem_read(void *priv, unsigned int offset, void *val,
> + size_t bytes)
> +{
> + const struct nvmem_cell_info *mem_cell;
> + struct adm1266_data *data = priv;
> + int ret;
> + int i;
> +
> + for (i = 0; i < data->nvmem_config.ncells; i++) {
> + mem_cell = &adm1266_nvmem_cells[i];
> + if (!adm1266_cell_is_accessed(mem_cell, offset, bytes))
> + continue;
> +
> + ret = adm1266_read_mem_cell(data, mem_cell);
> + if (ret < 0)
> + return ret;
> + }
> +
> + memcpy(val, data->dev_mem + offset, bytes);
> +
> + return 0;
> +}
> +
> +static int adm1266_config_nvmem(struct adm1266_data *data)
> +{
> + data->nvmem_config.name = dev_name(&data->client->dev);
> + data->nvmem_config.dev = &data->client->dev;
> + data->nvmem_config.root_only = true;
> + data->nvmem_config.read_only = true;
> + data->nvmem_config.owner = THIS_MODULE;
> + data->nvmem_config.reg_read = adm1266_nvmem_read;
> + data->nvmem_config.cells = adm1266_nvmem_cells;
> + data->nvmem_config.ncells = ARRAY_SIZE(adm1266_nvmem_cells);
> + data->nvmem_config.priv = data;
> + data->nvmem_config.stride = 1;
> + data->nvmem_config.word_size = 1;
> + data->nvmem_config.size = 0x80000;
> +
> + data->nvmem = nvmem_register(&data->nvmem_config);
> + if (IS_ERR(data->nvmem)) {
> + dev_err(&data->client->dev, "Could not register nvmem!");
> + return PTR_ERR(data->nvmem);
> + }
> +
> + data->dev_mem = devm_kzalloc(&data->client->dev,
> + data->nvmem_config.size,
> + GFP_KERNEL);
> + if (!data->dev_mem)
> + return -ENOMEM;
> +
> + return 0;
> }
>
> static int adm1266_probe(struct i2c_client *client,
> @@ -299,6 +455,10 @@ static int adm1266_probe(struct i2c_client *client,
> if (ret < 0)
> return ret;
>
> + ret = adm1266_config_nvmem(data);
> + if (ret < 0)
> + return ret;
> +
> adm1266_debug_init(data);
>
> info = &data->info;
>