brintos

brintos / linux-shallow public Read only

0
0
Text · 5.7 KiB · 34d0f06 Raw
250 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Hardware monitoring driver for the STPDDC60 controller4 *5 * Copyright (c) 2021 Flextronics International Sweden AB.6 */7 8#include <linux/kernel.h>9#include <linux/module.h>10#include <linux/init.h>11#include <linux/err.h>12#include <linux/i2c.h>13#include <linux/pmbus.h>14#include "pmbus.h"15 16#define STPDDC60_MFR_READ_VOUT		0xd217#define STPDDC60_MFR_OV_LIMIT_OFFSET	0xe518#define STPDDC60_MFR_UV_LIMIT_OFFSET	0xe619 20static const struct i2c_device_id stpddc60_id[] = {21	{"stpddc60"},22	{"bmr481"},23	{}24};25MODULE_DEVICE_TABLE(i2c, stpddc60_id);26 27static struct pmbus_driver_info stpddc60_info = {28	.pages = 1,29	.func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT30		| PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT31		| PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP32		| PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT33		| PMBUS_HAVE_POUT,34};35 36/*37 * Calculate the closest absolute offset between commanded vout value38 * and limit value in steps of 50mv in the range 0 (50mv) to 7 (400mv).39 * Return 0 if the upper limit is lower than vout or if the lower limit40 * is higher than vout.41 */42static u8 stpddc60_get_offset(int vout, u16 limit, bool over)43{44	int offset;45	long v, l;46 47	v = 250 + (vout - 1) * 5; /* Convert VID to mv */48	l = (limit * 1000L) >> 8; /* Convert LINEAR to mv */49 50	if (over == (l < v))51		return 0;52 53	offset = DIV_ROUND_CLOSEST(abs(l - v), 50);54 55	if (offset > 0)56		offset--;57 58	return clamp_val(offset, 0, 7);59}60 61/*62 * Adjust the linear format word to use the given fixed exponent.63 */64static u16 stpddc60_adjust_linear(u16 word, s16 fixed)65{66	s16 e, m, d;67 68	e = ((s16)word) >> 11;69	m = ((s16)((word & 0x7ff) << 5)) >> 5;70	d = e - fixed;71 72	if (d >= 0)73		m <<= d;74	else75		m >>= -d;76 77	return clamp_val(m, 0, 0x3ff) | ((fixed << 11) & 0xf800);78}79 80/*81 * The VOUT_COMMAND register uses the VID format but the vout alarm limit82 * registers use the LINEAR format so we override VOUT_MODE here to force83 * LINEAR format for all registers.84 */85static int stpddc60_read_byte_data(struct i2c_client *client, int page, int reg)86{87	int ret;88 89	if (page > 0)90		return -ENXIO;91 92	switch (reg) {93	case PMBUS_VOUT_MODE:94		ret = 0x18;95		break;96	default:97		ret = -ENODATA;98		break;99	}100 101	return ret;102}103 104/*105 * The vout related registers return values in LINEAR11 format when LINEAR16106 * is expected. Clear the top 5 bits to set the exponent part to zero to107 * convert the value to LINEAR16 format.108 */109static int stpddc60_read_word_data(struct i2c_client *client, int page,110				   int phase, int reg)111{112	int ret;113 114	if (page > 0)115		return -ENXIO;116 117	switch (reg) {118	case PMBUS_READ_VOUT:119		ret = pmbus_read_word_data(client, page, phase,120					   STPDDC60_MFR_READ_VOUT);121		if (ret < 0)122			return ret;123		ret &= 0x7ff;124		break;125	case PMBUS_VOUT_OV_FAULT_LIMIT:126	case PMBUS_VOUT_UV_FAULT_LIMIT:127		ret = pmbus_read_word_data(client, page, phase, reg);128		if (ret < 0)129			return ret;130		ret &= 0x7ff;131		break;132	default:133		ret = -ENODATA;134		break;135	}136 137	return ret;138}139 140/*141 * The vout under- and over-voltage limits are set as an offset relative to142 * the commanded vout voltage. The vin, iout, pout and temp limits must use143 * the same fixed exponent the chip uses to encode the data when read.144 */145static int stpddc60_write_word_data(struct i2c_client *client, int page,146				    int reg, u16 word)147{148	int ret;149	u8 offset;150 151	if (page > 0)152		return -ENXIO;153 154	switch (reg) {155	case PMBUS_VOUT_OV_FAULT_LIMIT:156		ret = pmbus_read_word_data(client, page, 0xff,157					   PMBUS_VOUT_COMMAND);158		if (ret < 0)159			return ret;160		offset = stpddc60_get_offset(ret, word, true);161		ret = pmbus_write_byte_data(client, page,162					    STPDDC60_MFR_OV_LIMIT_OFFSET,163					    offset);164		break;165	case PMBUS_VOUT_UV_FAULT_LIMIT:166		ret = pmbus_read_word_data(client, page, 0xff,167					   PMBUS_VOUT_COMMAND);168		if (ret < 0)169			return ret;170		offset = stpddc60_get_offset(ret, word, false);171		ret = pmbus_write_byte_data(client, page,172					    STPDDC60_MFR_UV_LIMIT_OFFSET,173					    offset);174		break;175	case PMBUS_VIN_OV_FAULT_LIMIT:176	case PMBUS_VIN_UV_FAULT_LIMIT:177	case PMBUS_OT_FAULT_LIMIT:178	case PMBUS_OT_WARN_LIMIT:179	case PMBUS_IOUT_OC_FAULT_LIMIT:180	case PMBUS_IOUT_OC_WARN_LIMIT:181	case PMBUS_POUT_OP_FAULT_LIMIT:182		ret = pmbus_read_word_data(client, page, 0xff, reg);183		if (ret < 0)184			return ret;185		word = stpddc60_adjust_linear(word, ret >> 11);186		ret = pmbus_write_word_data(client, page, reg, word);187		break;188	default:189		ret = -ENODATA;190		break;191	}192 193	return ret;194}195 196static int stpddc60_probe(struct i2c_client *client)197{198	int status;199	u8 device_id[I2C_SMBUS_BLOCK_MAX + 1];200	const struct i2c_device_id *mid;201	struct pmbus_driver_info *info = &stpddc60_info;202 203	if (!i2c_check_functionality(client->adapter,204				     I2C_FUNC_SMBUS_READ_BYTE_DATA205				     | I2C_FUNC_SMBUS_BLOCK_DATA))206		return -ENODEV;207 208	status = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, device_id);209	if (status < 0) {210		dev_err(&client->dev, "Failed to read Manufacturer Model\n");211		return status;212	}213	for (mid = stpddc60_id; mid->name[0]; mid++) {214		if (!strncasecmp(mid->name, device_id, strlen(mid->name)))215			break;216	}217	if (!mid->name[0]) {218		dev_err(&client->dev, "Unsupported device\n");219		return -ENODEV;220	}221 222	info->read_byte_data = stpddc60_read_byte_data;223	info->read_word_data = stpddc60_read_word_data;224	info->write_word_data = stpddc60_write_word_data;225 226	status = pmbus_do_probe(client, info);227	if (status < 0)228		return status;229 230	pmbus_set_update(client, PMBUS_VOUT_OV_FAULT_LIMIT, true);231	pmbus_set_update(client, PMBUS_VOUT_UV_FAULT_LIMIT, true);232 233	return 0;234}235 236static struct i2c_driver stpddc60_driver = {237	.driver = {238		   .name = "stpddc60",239		   },240	.probe = stpddc60_probe,241	.id_table = stpddc60_id,242};243 244module_i2c_driver(stpddc60_driver);245 246MODULE_AUTHOR("Erik Rosen <erik.rosen@metormote.com>");247MODULE_DESCRIPTION("PMBus driver for ST STPDDC60");248MODULE_LICENSE("GPL");249MODULE_IMPORT_NS(PMBUS);250