| 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 |
| |