On 5/29/20 6:05 AM, alexandru.tachici@xxxxxxxxxx wrote: > From: Alexandru Tachici <alexandru.tachici@xxxxxxxxxx> > > 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 <alexandru.tachici@xxxxxxxxxx> > --- > 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; >