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