brintos

brintos / linux-shallow public Read only

0
0
Text · 7.5 KiB · 543955c Raw
320 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Hardware monitoring driver for MPS Multi-phase Digital VR Controllers(MP9941)4 */5 6#include <linux/bitfield.h>7#include <linux/bits.h>8#include <linux/i2c.h>9#include <linux/module.h>10#include <linux/of_device.h>11#include "pmbus.h"12 13/*14 * Vender specific registers. The MFR_ICC_MAX(0x02) is used to15 * config the iin scale. The MFR_RESO_SET(0xC7) is used to16 * config the vout format. The MFR_VR_MULTI_CONFIG_R1(0x0D) is17 * used to identify the vout vid step.18 */19#define MFR_ICC_MAX	0x0220#define MFR_RESO_SET	0xC721#define MFR_VR_MULTI_CONFIG_R1	0x0D22 23#define MP9941_VIN_LIMIT_UINT	124#define MP9941_VIN_LIMIT_DIV	825#define MP9941_READ_VIN_UINT	126#define MP9941_READ_VIN_DIV	3227 28#define MP9941_PAGE_NUM	129 30#define MP9941_RAIL1_FUNC	(PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | \31							PMBUS_HAVE_IOUT | PMBUS_HAVE_POUT | \32							PMBUS_HAVE_TEMP | PMBUS_HAVE_PIN | \33							PMBUS_HAVE_IIN | \34							PMBUS_HAVE_STATUS_VOUT | \35							PMBUS_HAVE_STATUS_IOUT | \36							PMBUS_HAVE_STATUS_TEMP | \37							PMBUS_HAVE_STATUS_INPUT)38 39struct mp9941_data {40	struct pmbus_driver_info info;41	int vid_resolution;42};43 44#define to_mp9941_data(x) container_of(x, struct mp9941_data, info)45 46static int mp9941_set_vout_format(struct i2c_client *client)47{48	int ret;49 50	ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0);51	if (ret < 0)52		return ret;53 54	ret = i2c_smbus_read_word_data(client, MFR_RESO_SET);55	if (ret < 0)56		return ret;57 58	/*59	 * page = 0, MFR_RESO_SET[7:6] defines the vout format60	 * 2'b11 set the vout format as direct61	 */62	ret = (ret & ~GENMASK(7, 6)) | FIELD_PREP(GENMASK(7, 6), 3);63 64	return i2c_smbus_write_word_data(client, MFR_RESO_SET, ret);65}66 67static int68mp9941_identify_vid_resolution(struct i2c_client *client, struct pmbus_driver_info *info)69{70	struct mp9941_data *data = to_mp9941_data(info);71	int ret;72 73	/*74	 * page = 2, MFR_VR_MULTI_CONFIG_R1[4:4] defines rail1 vid step value75	 * 1'b0 represents the vid step value is 10mV76	 * 1'b1 represents the vid step value is 5mV77	 */78	ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 2);79	if (ret < 0)80		return ret;81 82	ret = i2c_smbus_read_word_data(client, MFR_VR_MULTI_CONFIG_R1);83	if (ret < 0)84		return ret;85 86	if (FIELD_GET(GENMASK(4, 4), ret))87		data->vid_resolution = 5;88	else89		data->vid_resolution = 10;90 91	return 0;92}93 94static int mp9941_identify_iin_scale(struct i2c_client *client)95{96	int ret;97 98	ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0);99	if (ret < 0)100		return ret;101 102	ret = i2c_smbus_read_word_data(client, MFR_RESO_SET);103	if (ret < 0)104		return ret;105 106	ret = (ret & ~GENMASK(3, 2)) | FIELD_PREP(GENMASK(3, 2), 0);107 108	ret = i2c_smbus_write_word_data(client, MFR_RESO_SET, ret);109	if (ret < 0)110		return ret;111 112	/*113	 * page = 2, MFR_ICC_MAX[15:13] defines the iin scale114	 * 3'b000 set the iout scale as 0.5A/Lsb115	 */116	ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 2);117	if (ret < 0)118		return ret;119 120	ret = i2c_smbus_read_word_data(client, MFR_ICC_MAX);121	if (ret < 0)122		return ret;123 124	ret = (ret & ~GENMASK(15, 13)) | FIELD_PREP(GENMASK(15, 13), 0);125 126	return i2c_smbus_write_word_data(client, MFR_ICC_MAX, ret);127}128 129static int mp9941_identify(struct i2c_client *client, struct pmbus_driver_info *info)130{131	int ret;132 133	ret = mp9941_identify_iin_scale(client);134	if (ret < 0)135		return ret;136 137	ret = mp9941_identify_vid_resolution(client, info);138	if (ret < 0)139		return ret;140 141	return mp9941_set_vout_format(client);142}143 144static int mp9941_read_word_data(struct i2c_client *client, int page, int phase,145				 int reg)146{147	const struct pmbus_driver_info *info = pmbus_get_driver_info(client);148	struct mp9941_data *data = to_mp9941_data(info);149	int ret;150 151	switch (reg) {152	case PMBUS_READ_VIN:153		/* The MP9941 vin scale is (1/32V)/Lsb */154		ret = pmbus_read_word_data(client, page, phase, reg);155		if (ret < 0)156			return ret;157 158		ret = DIV_ROUND_CLOSEST((ret & GENMASK(9, 0)) * MP9941_READ_VIN_UINT,159					MP9941_READ_VIN_DIV);160		break;161	case PMBUS_READ_IIN:162		ret = pmbus_read_word_data(client, page, phase, reg);163		if (ret < 0)164			return ret;165 166		ret = ret & GENMASK(10, 0);167		break;168	case PMBUS_VIN_OV_FAULT_LIMIT:169		/* The MP9941 vin ov limit scale is (1/8V)/Lsb */170		ret = pmbus_read_word_data(client, page, phase, reg);171		if (ret < 0)172			return ret;173 174		ret = DIV_ROUND_CLOSEST((ret & GENMASK(7, 0)) * MP9941_VIN_LIMIT_UINT,175					MP9941_VIN_LIMIT_DIV);176		break;177	case PMBUS_IIN_OC_WARN_LIMIT:178		ret = pmbus_read_word_data(client, page, phase, reg);179		if (ret < 0)180			return ret;181 182		ret = ret & GENMASK(7, 0);183		break;184	case PMBUS_VOUT_UV_FAULT_LIMIT:185	case PMBUS_MFR_VOUT_MIN:186	case PMBUS_MFR_VOUT_MAX:187		/*188		 * The vout scale is set to 1mV/Lsb(using r/m/b scale).189		 * But the vout uv limit and vout max/min scale is 1VID/Lsb,190		 * so the vout uv limit and vout max/min value should be191		 * multiplied by vid resolution.192		 */193		ret = pmbus_read_word_data(client, page, phase, reg);194		if (ret < 0)195			return ret;196 197		ret = ret * data->vid_resolution;198		break;199	case PMBUS_READ_IOUT:200	case PMBUS_READ_POUT:201	case PMBUS_READ_TEMPERATURE_1:202	case PMBUS_READ_VOUT:203	case PMBUS_READ_PIN:204	case PMBUS_OT_FAULT_LIMIT:205	case PMBUS_OT_WARN_LIMIT:206		ret = -ENODATA;207		break;208	default:209		ret = -EINVAL;210		break;211	}212 213	return ret;214}215 216static int mp9941_write_word_data(struct i2c_client *client, int page, int reg,217				  u16 word)218{219	const struct pmbus_driver_info *info = pmbus_get_driver_info(client);220	struct mp9941_data *data = to_mp9941_data(info);221	int ret;222 223	switch (reg) {224	case PMBUS_VIN_OV_FAULT_LIMIT:225		/* The MP9941 vin ov limit scale is (1/8V)/Lsb */226		ret = pmbus_write_word_data(client, page, reg,227					    DIV_ROUND_CLOSEST(word * MP9941_VIN_LIMIT_DIV,228							      MP9941_VIN_LIMIT_UINT));229		break;230	case PMBUS_VOUT_UV_FAULT_LIMIT:231	case PMBUS_MFR_VOUT_MIN:232	case PMBUS_MFR_VOUT_MAX:233		ret = pmbus_write_word_data(client, page, reg,234					    DIV_ROUND_CLOSEST(word, data->vid_resolution));235		break;236	case PMBUS_IIN_OC_WARN_LIMIT:237	case PMBUS_OT_FAULT_LIMIT:238	case PMBUS_OT_WARN_LIMIT:239		ret = -ENODATA;240		break;241	default:242		ret = -EINVAL;243		break;244	}245 246	return ret;247}248 249static const struct pmbus_driver_info mp9941_info = {250	.pages = MP9941_PAGE_NUM,251	.format[PSC_VOLTAGE_IN] = direct,252	.format[PSC_CURRENT_IN] = direct,253	.format[PSC_CURRENT_OUT] = linear,254	.format[PSC_POWER] = linear,255	.format[PSC_TEMPERATURE] = direct,256	.format[PSC_VOLTAGE_OUT] = direct,257 258	.m[PSC_TEMPERATURE] = 1,259	.R[PSC_TEMPERATURE] = 0,260	.b[PSC_TEMPERATURE] = 0,261 262	.m[PSC_VOLTAGE_IN] = 1,263	.R[PSC_VOLTAGE_IN] = 0,264	.b[PSC_VOLTAGE_IN] = 0,265 266	.m[PSC_CURRENT_IN] = 2,267	.R[PSC_CURRENT_IN] = 0,268	.b[PSC_CURRENT_IN] = 0,269 270	.m[PSC_VOLTAGE_OUT] = 1,271	.R[PSC_VOLTAGE_OUT] = 3,272	.b[PSC_VOLTAGE_OUT] = 0,273 274	.func[0] = MP9941_RAIL1_FUNC,275	.read_word_data = mp9941_read_word_data,276	.write_word_data = mp9941_write_word_data,277	.identify = mp9941_identify,278};279 280static int mp9941_probe(struct i2c_client *client)281{282	struct mp9941_data *data;283 284	data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);285	if (!data)286		return -ENOMEM;287 288	memcpy(&data->info, &mp9941_info, sizeof(mp9941_info));289 290	return pmbus_do_probe(client, &data->info);291}292 293static const struct i2c_device_id mp9941_id[] = {294	{"mp9941", 0},295	{}296};297MODULE_DEVICE_TABLE(i2c, mp9941_id);298 299static const struct of_device_id __maybe_unused mp9941_of_match[] = {300	{.compatible = "mps,mp9941"},301	{}302};303MODULE_DEVICE_TABLE(of, mp9941_of_match);304 305static struct i2c_driver mp9941_driver = {306	.driver = {307		.name = "mp9941",308		.of_match_table = mp9941_of_match,309	},310	.probe = mp9941_probe,311	.id_table = mp9941_id,312};313 314module_i2c_driver(mp9941_driver);315 316MODULE_AUTHOR("Noah Wang <noahwang.wang@outlook.com>");317MODULE_DESCRIPTION("PMBus driver for MPS MP9941");318MODULE_LICENSE("GPL");319MODULE_IMPORT_NS(PMBUS);320