| From cfbb521697185b418019ac05aa9e3c8ddc20c0a1 Mon Sep 17 00:00:00 2001 | |
| From: David Wang <davidwang@quantatw.com> | |
| Date: Thu, 25 May 2023 16:38:53 +0800 | |
| Subject: [PATCH] hwmon: max34451: Add programming feature | |
| Add feature to program sequencer's config in max34440 driver. | |
| Google-Bug-Id: 271369511 | |
| Signed-off-by: David Wang <davidwang@quantatw.com> | |
| --- | |
| drivers/hwmon/pmbus/max34440.c | 289 ++++++++++++++++++++++++++++++++- | |
| 1 file changed, 287 insertions(+), 2 deletions(-) | |
| diff --git a/drivers/hwmon/pmbus/max34440.c b/drivers/hwmon/pmbus/max34440.c | |
| index ac7d9c8823ab..44bed8253604 100644 | |
| --- a/drivers/hwmon/pmbus/max34440.c | |
| +++ b/drivers/hwmon/pmbus/max34440.c | |
| @@ -1,6 +1,6 @@ | |
| // SPDX-License-Identifier: GPL-2.0-or-later | |
| /* | |
| - * Hardware monitoring driver for Maxim MAX34440/MAX34441 | |
| + * Hardware monitoring driver for Maxim MAX34440/MAX34441/MAX34451 | |
| * | |
| * Copyright (c) 2011 Ericsson AB. | |
| * Copyright (c) 2012 Guenter Roeck | |
| @@ -13,6 +13,9 @@ | |
| #include <linux/err.h> | |
| #include <linux/i2c.h> | |
| #include <linux/delay.h> | |
| +#include <linux/firmware.h> | |
| +#include <linux/slab.h> | |
| +#include <linux/crc16.h> | |
| #include "pmbus.h" | |
| enum chips { max34440, max34441, max34446, max34451, max34460, max34461 }; | |
| @@ -42,13 +45,269 @@ enum chips { max34440, max34441, max34446, max34451, max34460, max34461 }; | |
| #define MAX34451_MFR_CHANNEL_CONFIG 0xe4 | |
| #define MAX34451_MFR_CHANNEL_CONFIG_SEL_MASK 0x3f | |
| +#define MAX34451_PROGRAM_PASSWORD "0penBmc" | |
| + | |
| struct max34440_data { | |
| int id; | |
| struct pmbus_driver_info info; | |
| + bool *enable_program; | |
| }; | |
| - | |
| #define to_max34440_data(x) container_of(x, struct max34440_data, info) | |
| +uint8_t ascii_to_hex(uint8_t c) | |
| +{ | |
| + if (c >= '0' && c <= '9') | |
| + return (uint8_t)(c - '0'); | |
| + | |
| + if (c >= 'A' && c <= 'F') | |
| + return (uint8_t)(c - 'A' + 10); | |
| + | |
| + if (c >= 'a' && c <= 'f') | |
| + return (uint8_t)(c - 'a' + 10); | |
| + | |
| + return 0; | |
| +} | |
| + | |
| +void GetValueInBytes(uint8_t *src, uint8_t *dest, uint8_t len) | |
| +{ | |
| + int i = 0; | |
| + for (i = 0; i < len; i++) | |
| + dest[i] = ascii_to_hex(src[i*2]) << 4 | ascii_to_hex(src[i*2+1]); | |
| + | |
| + return; | |
| +} | |
| + | |
| +static ssize_t main_flash_crc(struct device *dev, | |
| + struct device_attribute *attr, char *buf) | |
| +{ | |
| + struct i2c_client *client = to_i2c_client(dev->parent); | |
| + | |
| + int ret = 0; | |
| + ret = i2c_smbus_write_word_data(client, 0xFE, 0); | |
| + ret = i2c_smbus_read_word_data(client, 0xFE); | |
| + | |
| + if (ret < 0) | |
| + return 0; | |
| + | |
| + return snprintf(buf, PAGE_SIZE, "0x%02X\n", ret); | |
| +} | |
| + | |
| +static ssize_t backup_flash_crc(struct device *dev, | |
| + struct device_attribute *attr, char *buf) | |
| +{ | |
| + struct i2c_client *client = to_i2c_client(dev->parent); | |
| + | |
| + int ret = 0; | |
| + ret = i2c_smbus_write_word_data(client, 0xFE, 1); | |
| + ret = i2c_smbus_read_word_data(client, 0xFE); | |
| + | |
| + if (ret < 0) | |
| + return 0; | |
| + | |
| + return snprintf(buf, PAGE_SIZE, "0x%02X\n", ret); | |
| +} | |
| + | |
| +static ssize_t ram_crc(struct device *dev, | |
| + struct device_attribute *attr, char *buf) | |
| +{ | |
| + struct i2c_client *client = to_i2c_client(dev->parent); | |
| + | |
| + int ret = 0; | |
| + ret = i2c_smbus_write_word_data(client, 0xFE, 2); | |
| + ret = i2c_smbus_read_word_data(client, 0xFE); | |
| + | |
| + if (ret < 0) | |
| + return 0; | |
| + | |
| + return snprintf(buf, PAGE_SIZE, "0x%02X\n", ret); | |
| +} | |
| + | |
| +static ssize_t program_password(struct device *dev, | |
| + struct device_attribute *attr, | |
| + const char *buf, size_t len) | |
| +{ | |
| + char *password; | |
| + | |
| + struct i2c_client *client = to_i2c_client(dev->parent); | |
| + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); | |
| + const struct max34440_data *data = to_max34440_data(info); | |
| + | |
| + password = kstrdup(buf, GFP_KERNEL); | |
| + if (!password) { | |
| + dev_err(dev, "kstrdup failed\n"); | |
| + return -EINVAL; | |
| + } | |
| + | |
| + password = strim(password); | |
| + | |
| + if (strcmp(password, MAX34451_PROGRAM_PASSWORD)) | |
| + { | |
| + dev_err(dev, "password incorrect\n"); | |
| + return -EINVAL; | |
| + } | |
| + | |
| + *data->enable_program = true; | |
| + dev_info(dev, "program memory is enabled\n"); | |
| + kfree(password); | |
| + return len; | |
| +} | |
| + | |
| +static ssize_t program_memory(struct device *dev, | |
| + struct device_attribute *attr, | |
| + const char *buf, size_t len) | |
| +{ | |
| + int size; | |
| + const u8 *ptr; | |
| + char *fw_path; | |
| + const struct firmware *fw; | |
| + int ret = 0; | |
| + | |
| + int count = 0; | |
| + int dest_count = 0; | |
| + int data_len = 0; | |
| + | |
| + uint8_t address = 0; | |
| + uint8_t data_byte = 0; | |
| + uint8_t byte_count = 0; | |
| + uint8_t bytes[9] = {0}; | |
| + | |
| + uint16_t crcCalc = 0; | |
| + uint16_t data_word = 0; | |
| + | |
| + struct i2c_client *client = to_i2c_client(dev->parent); | |
| + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); | |
| + const struct max34440_data *data = to_max34440_data(info); | |
| + | |
| + if (!*data->enable_program) | |
| + { | |
| + dev_err(dev, "program memory permission denied\n"); | |
| + return -EINVAL; | |
| + } | |
| + | |
| + fw_path = kstrdup(buf, GFP_ATOMIC); | |
| + if (!fw_path) { | |
| + dev_err(dev, "kstrdup failed\n"); | |
| + return -EINVAL; | |
| + } | |
| + | |
| + fw_path = strim(fw_path); | |
| + | |
| + if (len < 1) | |
| + return -EINVAL; | |
| + | |
| + ret = request_firmware(&fw, fw_path, dev); | |
| + | |
| + if (ret < 0 || !fw->data || !fw->size) { | |
| + dev_err(dev, "Failed to get hex file\n"); | |
| + return ret; | |
| + } | |
| + | |
| + size = fw->size; | |
| + ptr = fw->data; | |
| + //Copy memory | |
| + for (count = 0; count < size; count++) { | |
| + if (ptr[count] == ':') { | |
| + data_len = ascii_to_hex(ptr[count+1]) << 4 | ascii_to_hex(ptr[count+2]); | |
| + if (data_len != 0) { | |
| + memmove(ptr+dest_count, ptr+count+9, data_len*2); | |
| + dest_count = dest_count+data_len*2; | |
| + } | |
| + } | |
| + } | |
| + | |
| + for (count = 0; count < dest_count; count += 2) { | |
| + ret = 0; | |
| + | |
| + data_len = ascii_to_hex(ptr[count]) << 4 | ascii_to_hex(ptr[count+1]); | |
| + count += 2; | |
| + | |
| + address = ascii_to_hex(ptr[count]) << 4 | ascii_to_hex(ptr[count+1]); | |
| + count += 2; | |
| + | |
| + switch (data_len) { | |
| + case 1: | |
| + { | |
| + data_byte = ascii_to_hex(ptr[count]) << 4 | ascii_to_hex(ptr[(count+1)]); | |
| + ret = i2c_smbus_write_byte_data(client, address, data_byte); | |
| + if (ret < 0) | |
| + ret = i2c_smbus_write_byte_data(client, address, data_byte); | |
| + | |
| + if (address != 00) { | |
| + crcCalc = crc16(crcCalc, bytes, 1); | |
| + byte_count++; | |
| + } | |
| + break; | |
| + } | |
| + case 2: | |
| + { | |
| + data_word = ascii_to_hex(ptr[count+2]) << 12 | ascii_to_hex(ptr[count+3]) << 8 | ascii_to_hex(ptr[count]) << 4 | ascii_to_hex(ptr[count+1]); | |
| + ret = i2c_smbus_write_word_data(client, address, data_word); | |
| + if (ret < 0) | |
| + ret = i2c_smbus_write_word_data(client, address, data_word); | |
| + crcCalc = crc16(crcCalc, bytes, 2); | |
| + count += data_len*2-2; | |
| + byte_count += 2; | |
| + break; | |
| + } | |
| + case 4: | |
| + { | |
| + GetValueInBytes(ptr+count, bytes, 4); | |
| + memcpy(bytes+1, bytes, 4); | |
| + bytes[0] = address; | |
| + | |
| + ret = i2c_master_send(client, bytes, 5); | |
| + if (ret < 0) | |
| + ret = i2c_master_send(client, bytes, 5); | |
| + | |
| + crcCalc = crc16(crcCalc, bytes, 4); | |
| + count += data_len*2-2; | |
| + byte_count += 4; | |
| + break; | |
| + } | |
| + default: | |
| + break; | |
| + } | |
| + | |
| + if (ret < 0) | |
| + dev_err(dev, "Configuration File Register Write Error, Addr: 0x%X", address); | |
| + | |
| + if ((count+9) > size) | |
| + break; | |
| + } | |
| + | |
| + dev_info(dev, "Configuration file is loaded, CRC of the file:0x%02X", crcCalc); | |
| + dev_info(dev, "program memory is disabled\n"); | |
| + *data->enable_program = false; | |
| + release_firmware(fw); | |
| + kfree(fw_path); | |
| + | |
| + return len; | |
| +} | |
| + | |
| +static DEVICE_ATTR(program_password, 0200, NULL, program_password); | |
| +static DEVICE_ATTR(program_memory, 0200, NULL, program_memory); | |
| +static DEVICE_ATTR(main_flash_crc, 0444, main_flash_crc, NULL); | |
| +static DEVICE_ATTR(backup_flash_crc, 0444, backup_flash_crc, NULL); | |
| +static DEVICE_ATTR(ram_crc, 0444, ram_crc, NULL); | |
| + | |
| +static struct attribute *max34440_attributes[] = { | |
| + &dev_attr_program_password.attr, | |
| + &dev_attr_program_memory.attr, | |
| + &dev_attr_main_flash_crc.attr, | |
| + &dev_attr_backup_flash_crc.attr, | |
| + &dev_attr_ram_crc.attr, | |
| + NULL | |
| +}; | |
| + | |
| +static struct attribute_group max34440_attr_group = { | |
| + .attrs = max34440_attributes, | |
| +}; | |
| + | |
| +static const struct attribute_group *dev_attr_groups[] = { | |
| + &max34440_attr_group, | |
| + NULL, | |
| +}; | |
| + | |
| static const struct i2c_device_id max34440_id[]; | |
| static int max34440_read_word_data(struct i2c_client *client, int page, | |
| @@ -494,8 +753,12 @@ static int max34440_probe(struct i2c_client *client) | |
| GFP_KERNEL); | |
| if (!data) | |
| return -ENOMEM; | |
| + | |
| data->id = i2c_match_id(max34440_id, client)->driver_data; | |
| data->info = max34440_info[data->id]; | |
| + data->info.groups = dev_attr_groups; | |
| + data->enable_program = devm_kzalloc(&client->dev, sizeof(bool), GFP_KERNEL); | |
| + *data->enable_program = false; | |
| if (data->id == max34451) { | |
| rv = max34451_set_supported_funcs(client, data); | |
| @@ -506,6 +769,27 @@ static int max34440_probe(struct i2c_client *client) | |
| return pmbus_do_probe(client, &data->info); | |
| } | |
| +static const struct of_device_id max34440_match[] = { | |
| + { | |
| + .compatible = "maxim,max34440", | |
| + }, | |
| + { | |
| + .compatible = "maxim,max34441", | |
| + }, | |
| + { | |
| + .compatible = "maxim,max34446", | |
| + }, | |
| + { | |
| + .compatible = "maxim,max34451", | |
| + }, | |
| + { | |
| + .compatible = "maxim,max34460", | |
| + }, | |
| + { | |
| + .compatible = "maxim,max34461", | |
| + }, | |
| + {}, | |
| +}; | |
| static const struct i2c_device_id max34440_id[] = { | |
| {"max34440", max34440}, | |
| {"max34441", max34441}, | |
| @@ -521,6 +805,7 @@ MODULE_DEVICE_TABLE(i2c, max34440_id); | |
| static struct i2c_driver max34440_driver = { | |
| .driver = { | |
| .name = "max34440", | |
| + .of_match_table = of_match_ptr(max34440_match), | |
| }, | |
| .probe_new = max34440_probe, | |
| .id_table = max34440_id, | |
| -- | |
| 2.25.1 | |