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