blob: 6026139716f0a86f26f76f4016e35ce6d549cd1e [file] [log] [blame]
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