xref: /openbmc/linux/drivers/hwmon/pmbus/stpddc60.c (revision 2612e3bbc0386368a850140a6c9b990cd496a5ec)
142bfe7ddSErik Rosen // SPDX-License-Identifier: GPL-2.0-or-later
242bfe7ddSErik Rosen /*
342bfe7ddSErik Rosen  * Hardware monitoring driver for the STPDDC60 controller
442bfe7ddSErik Rosen  *
542bfe7ddSErik Rosen  * Copyright (c) 2021 Flextronics International Sweden AB.
642bfe7ddSErik Rosen  */
742bfe7ddSErik Rosen 
842bfe7ddSErik Rosen #include <linux/kernel.h>
942bfe7ddSErik Rosen #include <linux/module.h>
1042bfe7ddSErik Rosen #include <linux/init.h>
1142bfe7ddSErik Rosen #include <linux/err.h>
1242bfe7ddSErik Rosen #include <linux/i2c.h>
1342bfe7ddSErik Rosen #include <linux/pmbus.h>
1442bfe7ddSErik Rosen #include "pmbus.h"
1542bfe7ddSErik Rosen 
1642bfe7ddSErik Rosen #define STPDDC60_MFR_READ_VOUT		0xd2
1742bfe7ddSErik Rosen #define STPDDC60_MFR_OV_LIMIT_OFFSET	0xe5
1842bfe7ddSErik Rosen #define STPDDC60_MFR_UV_LIMIT_OFFSET	0xe6
1942bfe7ddSErik Rosen 
2042bfe7ddSErik Rosen static const struct i2c_device_id stpddc60_id[] = {
2142bfe7ddSErik Rosen 	{"stpddc60", 0},
2242bfe7ddSErik Rosen 	{"bmr481", 0},
2342bfe7ddSErik Rosen 	{}
2442bfe7ddSErik Rosen };
2542bfe7ddSErik Rosen MODULE_DEVICE_TABLE(i2c, stpddc60_id);
2642bfe7ddSErik Rosen 
2742bfe7ddSErik Rosen static struct pmbus_driver_info stpddc60_info = {
2842bfe7ddSErik Rosen 	.pages = 1,
2942bfe7ddSErik Rosen 	.func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT
3042bfe7ddSErik Rosen 		| PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT
3142bfe7ddSErik Rosen 		| PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP
3242bfe7ddSErik Rosen 		| PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT
3342bfe7ddSErik Rosen 		| PMBUS_HAVE_POUT,
3442bfe7ddSErik Rosen };
3542bfe7ddSErik Rosen 
3642bfe7ddSErik Rosen /*
3742bfe7ddSErik Rosen  * Calculate the closest absolute offset between commanded vout value
3842bfe7ddSErik Rosen  * and limit value in steps of 50mv in the range 0 (50mv) to 7 (400mv).
3942bfe7ddSErik Rosen  * Return 0 if the upper limit is lower than vout or if the lower limit
4042bfe7ddSErik Rosen  * is higher than vout.
4142bfe7ddSErik Rosen  */
stpddc60_get_offset(int vout,u16 limit,bool over)4242bfe7ddSErik Rosen static u8 stpddc60_get_offset(int vout, u16 limit, bool over)
4342bfe7ddSErik Rosen {
4442bfe7ddSErik Rosen 	int offset;
4542bfe7ddSErik Rosen 	long v, l;
4642bfe7ddSErik Rosen 
4742bfe7ddSErik Rosen 	v = 250 + (vout - 1) * 5; /* Convert VID to mv */
4842bfe7ddSErik Rosen 	l = (limit * 1000L) >> 8; /* Convert LINEAR to mv */
4942bfe7ddSErik Rosen 
5042bfe7ddSErik Rosen 	if (over == (l < v))
5142bfe7ddSErik Rosen 		return 0;
5242bfe7ddSErik Rosen 
5342bfe7ddSErik Rosen 	offset = DIV_ROUND_CLOSEST(abs(l - v), 50);
5442bfe7ddSErik Rosen 
5542bfe7ddSErik Rosen 	if (offset > 0)
5642bfe7ddSErik Rosen 		offset--;
5742bfe7ddSErik Rosen 
5842bfe7ddSErik Rosen 	return clamp_val(offset, 0, 7);
5942bfe7ddSErik Rosen }
6042bfe7ddSErik Rosen 
6142bfe7ddSErik Rosen /*
6242bfe7ddSErik Rosen  * Adjust the linear format word to use the given fixed exponent.
6342bfe7ddSErik Rosen  */
stpddc60_adjust_linear(u16 word,s16 fixed)6442bfe7ddSErik Rosen static u16 stpddc60_adjust_linear(u16 word, s16 fixed)
6542bfe7ddSErik Rosen {
6642bfe7ddSErik Rosen 	s16 e, m, d;
6742bfe7ddSErik Rosen 
6842bfe7ddSErik Rosen 	e = ((s16)word) >> 11;
6942bfe7ddSErik Rosen 	m = ((s16)((word & 0x7ff) << 5)) >> 5;
7042bfe7ddSErik Rosen 	d = e - fixed;
7142bfe7ddSErik Rosen 
7242bfe7ddSErik Rosen 	if (d >= 0)
7342bfe7ddSErik Rosen 		m <<= d;
7442bfe7ddSErik Rosen 	else
7542bfe7ddSErik Rosen 		m >>= -d;
7642bfe7ddSErik Rosen 
7742bfe7ddSErik Rosen 	return clamp_val(m, 0, 0x3ff) | ((fixed << 11) & 0xf800);
7842bfe7ddSErik Rosen }
7942bfe7ddSErik Rosen 
8042bfe7ddSErik Rosen /*
8142bfe7ddSErik Rosen  * The VOUT_COMMAND register uses the VID format but the vout alarm limit
8242bfe7ddSErik Rosen  * registers use the LINEAR format so we override VOUT_MODE here to force
8342bfe7ddSErik Rosen  * LINEAR format for all registers.
8442bfe7ddSErik Rosen  */
stpddc60_read_byte_data(struct i2c_client * client,int page,int reg)8542bfe7ddSErik Rosen static int stpddc60_read_byte_data(struct i2c_client *client, int page, int reg)
8642bfe7ddSErik Rosen {
8742bfe7ddSErik Rosen 	int ret;
8842bfe7ddSErik Rosen 
8942bfe7ddSErik Rosen 	if (page > 0)
9042bfe7ddSErik Rosen 		return -ENXIO;
9142bfe7ddSErik Rosen 
9242bfe7ddSErik Rosen 	switch (reg) {
9342bfe7ddSErik Rosen 	case PMBUS_VOUT_MODE:
9442bfe7ddSErik Rosen 		ret = 0x18;
9542bfe7ddSErik Rosen 		break;
9642bfe7ddSErik Rosen 	default:
9742bfe7ddSErik Rosen 		ret = -ENODATA;
9842bfe7ddSErik Rosen 		break;
9942bfe7ddSErik Rosen 	}
10042bfe7ddSErik Rosen 
10142bfe7ddSErik Rosen 	return ret;
10242bfe7ddSErik Rosen }
10342bfe7ddSErik Rosen 
10442bfe7ddSErik Rosen /*
10542bfe7ddSErik Rosen  * The vout related registers return values in LINEAR11 format when LINEAR16
10642bfe7ddSErik Rosen  * is expected. Clear the top 5 bits to set the exponent part to zero to
10742bfe7ddSErik Rosen  * convert the value to LINEAR16 format.
10842bfe7ddSErik Rosen  */
stpddc60_read_word_data(struct i2c_client * client,int page,int phase,int reg)10942bfe7ddSErik Rosen static int stpddc60_read_word_data(struct i2c_client *client, int page,
11042bfe7ddSErik Rosen 				   int phase, int reg)
11142bfe7ddSErik Rosen {
11242bfe7ddSErik Rosen 	int ret;
11342bfe7ddSErik Rosen 
11442bfe7ddSErik Rosen 	if (page > 0)
11542bfe7ddSErik Rosen 		return -ENXIO;
11642bfe7ddSErik Rosen 
11742bfe7ddSErik Rosen 	switch (reg) {
11842bfe7ddSErik Rosen 	case PMBUS_READ_VOUT:
11942bfe7ddSErik Rosen 		ret = pmbus_read_word_data(client, page, phase,
12042bfe7ddSErik Rosen 					   STPDDC60_MFR_READ_VOUT);
12142bfe7ddSErik Rosen 		if (ret < 0)
12242bfe7ddSErik Rosen 			return ret;
12342bfe7ddSErik Rosen 		ret &= 0x7ff;
12442bfe7ddSErik Rosen 		break;
12542bfe7ddSErik Rosen 	case PMBUS_VOUT_OV_FAULT_LIMIT:
12642bfe7ddSErik Rosen 	case PMBUS_VOUT_UV_FAULT_LIMIT:
12742bfe7ddSErik Rosen 		ret = pmbus_read_word_data(client, page, phase, reg);
12842bfe7ddSErik Rosen 		if (ret < 0)
12942bfe7ddSErik Rosen 			return ret;
13042bfe7ddSErik Rosen 		ret &= 0x7ff;
13142bfe7ddSErik Rosen 		break;
13242bfe7ddSErik Rosen 	default:
13342bfe7ddSErik Rosen 		ret = -ENODATA;
13442bfe7ddSErik Rosen 		break;
13542bfe7ddSErik Rosen 	}
13642bfe7ddSErik Rosen 
13742bfe7ddSErik Rosen 	return ret;
13842bfe7ddSErik Rosen }
13942bfe7ddSErik Rosen 
14042bfe7ddSErik Rosen /*
14142bfe7ddSErik Rosen  * The vout under- and over-voltage limits are set as an offset relative to
14242bfe7ddSErik Rosen  * the commanded vout voltage. The vin, iout, pout and temp limits must use
14342bfe7ddSErik Rosen  * the same fixed exponent the chip uses to encode the data when read.
14442bfe7ddSErik Rosen  */
stpddc60_write_word_data(struct i2c_client * client,int page,int reg,u16 word)14542bfe7ddSErik Rosen static int stpddc60_write_word_data(struct i2c_client *client, int page,
14642bfe7ddSErik Rosen 				    int reg, u16 word)
14742bfe7ddSErik Rosen {
14842bfe7ddSErik Rosen 	int ret;
14942bfe7ddSErik Rosen 	u8 offset;
15042bfe7ddSErik Rosen 
15142bfe7ddSErik Rosen 	if (page > 0)
15242bfe7ddSErik Rosen 		return -ENXIO;
15342bfe7ddSErik Rosen 
15442bfe7ddSErik Rosen 	switch (reg) {
15542bfe7ddSErik Rosen 	case PMBUS_VOUT_OV_FAULT_LIMIT:
15642bfe7ddSErik Rosen 		ret = pmbus_read_word_data(client, page, 0xff,
15742bfe7ddSErik Rosen 					   PMBUS_VOUT_COMMAND);
15842bfe7ddSErik Rosen 		if (ret < 0)
15942bfe7ddSErik Rosen 			return ret;
16042bfe7ddSErik Rosen 		offset = stpddc60_get_offset(ret, word, true);
16142bfe7ddSErik Rosen 		ret = pmbus_write_byte_data(client, page,
16242bfe7ddSErik Rosen 					    STPDDC60_MFR_OV_LIMIT_OFFSET,
16342bfe7ddSErik Rosen 					    offset);
16442bfe7ddSErik Rosen 		break;
16542bfe7ddSErik Rosen 	case PMBUS_VOUT_UV_FAULT_LIMIT:
16642bfe7ddSErik Rosen 		ret = pmbus_read_word_data(client, page, 0xff,
16742bfe7ddSErik Rosen 					   PMBUS_VOUT_COMMAND);
16842bfe7ddSErik Rosen 		if (ret < 0)
16942bfe7ddSErik Rosen 			return ret;
17042bfe7ddSErik Rosen 		offset = stpddc60_get_offset(ret, word, false);
17142bfe7ddSErik Rosen 		ret = pmbus_write_byte_data(client, page,
17242bfe7ddSErik Rosen 					    STPDDC60_MFR_UV_LIMIT_OFFSET,
17342bfe7ddSErik Rosen 					    offset);
17442bfe7ddSErik Rosen 		break;
17542bfe7ddSErik Rosen 	case PMBUS_VIN_OV_FAULT_LIMIT:
17642bfe7ddSErik Rosen 	case PMBUS_VIN_UV_FAULT_LIMIT:
17742bfe7ddSErik Rosen 	case PMBUS_OT_FAULT_LIMIT:
17842bfe7ddSErik Rosen 	case PMBUS_OT_WARN_LIMIT:
17942bfe7ddSErik Rosen 	case PMBUS_IOUT_OC_FAULT_LIMIT:
18042bfe7ddSErik Rosen 	case PMBUS_IOUT_OC_WARN_LIMIT:
18142bfe7ddSErik Rosen 	case PMBUS_POUT_OP_FAULT_LIMIT:
18242bfe7ddSErik Rosen 		ret = pmbus_read_word_data(client, page, 0xff, reg);
18342bfe7ddSErik Rosen 		if (ret < 0)
18442bfe7ddSErik Rosen 			return ret;
18542bfe7ddSErik Rosen 		word = stpddc60_adjust_linear(word, ret >> 11);
18642bfe7ddSErik Rosen 		ret = pmbus_write_word_data(client, page, reg, word);
18742bfe7ddSErik Rosen 		break;
18842bfe7ddSErik Rosen 	default:
18942bfe7ddSErik Rosen 		ret = -ENODATA;
19042bfe7ddSErik Rosen 		break;
19142bfe7ddSErik Rosen 	}
19242bfe7ddSErik Rosen 
19342bfe7ddSErik Rosen 	return ret;
19442bfe7ddSErik Rosen }
19542bfe7ddSErik Rosen 
stpddc60_probe(struct i2c_client * client)19642bfe7ddSErik Rosen static int stpddc60_probe(struct i2c_client *client)
19742bfe7ddSErik Rosen {
19842bfe7ddSErik Rosen 	int status;
19942bfe7ddSErik Rosen 	u8 device_id[I2C_SMBUS_BLOCK_MAX + 1];
20042bfe7ddSErik Rosen 	const struct i2c_device_id *mid;
20142bfe7ddSErik Rosen 	struct pmbus_driver_info *info = &stpddc60_info;
20242bfe7ddSErik Rosen 
20342bfe7ddSErik Rosen 	if (!i2c_check_functionality(client->adapter,
20442bfe7ddSErik Rosen 				     I2C_FUNC_SMBUS_READ_BYTE_DATA
20542bfe7ddSErik Rosen 				     | I2C_FUNC_SMBUS_BLOCK_DATA))
20642bfe7ddSErik Rosen 		return -ENODEV;
20742bfe7ddSErik Rosen 
20842bfe7ddSErik Rosen 	status = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, device_id);
20942bfe7ddSErik Rosen 	if (status < 0) {
21042bfe7ddSErik Rosen 		dev_err(&client->dev, "Failed to read Manufacturer Model\n");
21142bfe7ddSErik Rosen 		return status;
21242bfe7ddSErik Rosen 	}
21342bfe7ddSErik Rosen 	for (mid = stpddc60_id; mid->name[0]; mid++) {
21442bfe7ddSErik Rosen 		if (!strncasecmp(mid->name, device_id, strlen(mid->name)))
21542bfe7ddSErik Rosen 			break;
21642bfe7ddSErik Rosen 	}
21742bfe7ddSErik Rosen 	if (!mid->name[0]) {
21842bfe7ddSErik Rosen 		dev_err(&client->dev, "Unsupported device\n");
21942bfe7ddSErik Rosen 		return -ENODEV;
22042bfe7ddSErik Rosen 	}
22142bfe7ddSErik Rosen 
22242bfe7ddSErik Rosen 	info->read_byte_data = stpddc60_read_byte_data;
22342bfe7ddSErik Rosen 	info->read_word_data = stpddc60_read_word_data;
22442bfe7ddSErik Rosen 	info->write_word_data = stpddc60_write_word_data;
22542bfe7ddSErik Rosen 
22642bfe7ddSErik Rosen 	status = pmbus_do_probe(client, info);
22742bfe7ddSErik Rosen 	if (status < 0)
22842bfe7ddSErik Rosen 		return status;
22942bfe7ddSErik Rosen 
23042bfe7ddSErik Rosen 	pmbus_set_update(client, PMBUS_VOUT_OV_FAULT_LIMIT, true);
23142bfe7ddSErik Rosen 	pmbus_set_update(client, PMBUS_VOUT_UV_FAULT_LIMIT, true);
23242bfe7ddSErik Rosen 
23342bfe7ddSErik Rosen 	return 0;
23442bfe7ddSErik Rosen }
23542bfe7ddSErik Rosen 
23642bfe7ddSErik Rosen static struct i2c_driver stpddc60_driver = {
23742bfe7ddSErik Rosen 	.driver = {
23842bfe7ddSErik Rosen 		   .name = "stpddc60",
23942bfe7ddSErik Rosen 		   },
240*1975d167SUwe Kleine-König 	.probe = stpddc60_probe,
24142bfe7ddSErik Rosen 	.id_table = stpddc60_id,
24242bfe7ddSErik Rosen };
24342bfe7ddSErik Rosen 
24442bfe7ddSErik Rosen module_i2c_driver(stpddc60_driver);
24542bfe7ddSErik Rosen 
24642bfe7ddSErik Rosen MODULE_AUTHOR("Erik Rosen <erik.rosen@metormote.com>");
24742bfe7ddSErik Rosen MODULE_DESCRIPTION("PMBus driver for ST STPDDC60");
24842bfe7ddSErik Rosen MODULE_LICENSE("GPL");
249b94ca77eSGuenter Roeck MODULE_IMPORT_NS(PMBUS);
250