162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Hardware monitoring driver for the STPDDC60 controller
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (c) 2021 Flextronics International Sweden AB.
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/kernel.h>
962306a36Sopenharmony_ci#include <linux/module.h>
1062306a36Sopenharmony_ci#include <linux/init.h>
1162306a36Sopenharmony_ci#include <linux/err.h>
1262306a36Sopenharmony_ci#include <linux/i2c.h>
1362306a36Sopenharmony_ci#include <linux/pmbus.h>
1462306a36Sopenharmony_ci#include "pmbus.h"
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#define STPDDC60_MFR_READ_VOUT		0xd2
1762306a36Sopenharmony_ci#define STPDDC60_MFR_OV_LIMIT_OFFSET	0xe5
1862306a36Sopenharmony_ci#define STPDDC60_MFR_UV_LIMIT_OFFSET	0xe6
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_cistatic const struct i2c_device_id stpddc60_id[] = {
2162306a36Sopenharmony_ci	{"stpddc60", 0},
2262306a36Sopenharmony_ci	{"bmr481", 0},
2362306a36Sopenharmony_ci	{}
2462306a36Sopenharmony_ci};
2562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, stpddc60_id);
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_cistatic struct pmbus_driver_info stpddc60_info = {
2862306a36Sopenharmony_ci	.pages = 1,
2962306a36Sopenharmony_ci	.func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT
3062306a36Sopenharmony_ci		| PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT
3162306a36Sopenharmony_ci		| PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP
3262306a36Sopenharmony_ci		| PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT
3362306a36Sopenharmony_ci		| PMBUS_HAVE_POUT,
3462306a36Sopenharmony_ci};
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci/*
3762306a36Sopenharmony_ci * Calculate the closest absolute offset between commanded vout value
3862306a36Sopenharmony_ci * and limit value in steps of 50mv in the range 0 (50mv) to 7 (400mv).
3962306a36Sopenharmony_ci * Return 0 if the upper limit is lower than vout or if the lower limit
4062306a36Sopenharmony_ci * is higher than vout.
4162306a36Sopenharmony_ci */
4262306a36Sopenharmony_cistatic u8 stpddc60_get_offset(int vout, u16 limit, bool over)
4362306a36Sopenharmony_ci{
4462306a36Sopenharmony_ci	int offset;
4562306a36Sopenharmony_ci	long v, l;
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	v = 250 + (vout - 1) * 5; /* Convert VID to mv */
4862306a36Sopenharmony_ci	l = (limit * 1000L) >> 8; /* Convert LINEAR to mv */
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	if (over == (l < v))
5162306a36Sopenharmony_ci		return 0;
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	offset = DIV_ROUND_CLOSEST(abs(l - v), 50);
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	if (offset > 0)
5662306a36Sopenharmony_ci		offset--;
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	return clamp_val(offset, 0, 7);
5962306a36Sopenharmony_ci}
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci/*
6262306a36Sopenharmony_ci * Adjust the linear format word to use the given fixed exponent.
6362306a36Sopenharmony_ci */
6462306a36Sopenharmony_cistatic u16 stpddc60_adjust_linear(u16 word, s16 fixed)
6562306a36Sopenharmony_ci{
6662306a36Sopenharmony_ci	s16 e, m, d;
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	e = ((s16)word) >> 11;
6962306a36Sopenharmony_ci	m = ((s16)((word & 0x7ff) << 5)) >> 5;
7062306a36Sopenharmony_ci	d = e - fixed;
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	if (d >= 0)
7362306a36Sopenharmony_ci		m <<= d;
7462306a36Sopenharmony_ci	else
7562306a36Sopenharmony_ci		m >>= -d;
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	return clamp_val(m, 0, 0x3ff) | ((fixed << 11) & 0xf800);
7862306a36Sopenharmony_ci}
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci/*
8162306a36Sopenharmony_ci * The VOUT_COMMAND register uses the VID format but the vout alarm limit
8262306a36Sopenharmony_ci * registers use the LINEAR format so we override VOUT_MODE here to force
8362306a36Sopenharmony_ci * LINEAR format for all registers.
8462306a36Sopenharmony_ci */
8562306a36Sopenharmony_cistatic int stpddc60_read_byte_data(struct i2c_client *client, int page, int reg)
8662306a36Sopenharmony_ci{
8762306a36Sopenharmony_ci	int ret;
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	if (page > 0)
9062306a36Sopenharmony_ci		return -ENXIO;
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	switch (reg) {
9362306a36Sopenharmony_ci	case PMBUS_VOUT_MODE:
9462306a36Sopenharmony_ci		ret = 0x18;
9562306a36Sopenharmony_ci		break;
9662306a36Sopenharmony_ci	default:
9762306a36Sopenharmony_ci		ret = -ENODATA;
9862306a36Sopenharmony_ci		break;
9962306a36Sopenharmony_ci	}
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	return ret;
10262306a36Sopenharmony_ci}
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci/*
10562306a36Sopenharmony_ci * The vout related registers return values in LINEAR11 format when LINEAR16
10662306a36Sopenharmony_ci * is expected. Clear the top 5 bits to set the exponent part to zero to
10762306a36Sopenharmony_ci * convert the value to LINEAR16 format.
10862306a36Sopenharmony_ci */
10962306a36Sopenharmony_cistatic int stpddc60_read_word_data(struct i2c_client *client, int page,
11062306a36Sopenharmony_ci				   int phase, int reg)
11162306a36Sopenharmony_ci{
11262306a36Sopenharmony_ci	int ret;
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	if (page > 0)
11562306a36Sopenharmony_ci		return -ENXIO;
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	switch (reg) {
11862306a36Sopenharmony_ci	case PMBUS_READ_VOUT:
11962306a36Sopenharmony_ci		ret = pmbus_read_word_data(client, page, phase,
12062306a36Sopenharmony_ci					   STPDDC60_MFR_READ_VOUT);
12162306a36Sopenharmony_ci		if (ret < 0)
12262306a36Sopenharmony_ci			return ret;
12362306a36Sopenharmony_ci		ret &= 0x7ff;
12462306a36Sopenharmony_ci		break;
12562306a36Sopenharmony_ci	case PMBUS_VOUT_OV_FAULT_LIMIT:
12662306a36Sopenharmony_ci	case PMBUS_VOUT_UV_FAULT_LIMIT:
12762306a36Sopenharmony_ci		ret = pmbus_read_word_data(client, page, phase, reg);
12862306a36Sopenharmony_ci		if (ret < 0)
12962306a36Sopenharmony_ci			return ret;
13062306a36Sopenharmony_ci		ret &= 0x7ff;
13162306a36Sopenharmony_ci		break;
13262306a36Sopenharmony_ci	default:
13362306a36Sopenharmony_ci		ret = -ENODATA;
13462306a36Sopenharmony_ci		break;
13562306a36Sopenharmony_ci	}
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	return ret;
13862306a36Sopenharmony_ci}
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci/*
14162306a36Sopenharmony_ci * The vout under- and over-voltage limits are set as an offset relative to
14262306a36Sopenharmony_ci * the commanded vout voltage. The vin, iout, pout and temp limits must use
14362306a36Sopenharmony_ci * the same fixed exponent the chip uses to encode the data when read.
14462306a36Sopenharmony_ci */
14562306a36Sopenharmony_cistatic int stpddc60_write_word_data(struct i2c_client *client, int page,
14662306a36Sopenharmony_ci				    int reg, u16 word)
14762306a36Sopenharmony_ci{
14862306a36Sopenharmony_ci	int ret;
14962306a36Sopenharmony_ci	u8 offset;
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	if (page > 0)
15262306a36Sopenharmony_ci		return -ENXIO;
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	switch (reg) {
15562306a36Sopenharmony_ci	case PMBUS_VOUT_OV_FAULT_LIMIT:
15662306a36Sopenharmony_ci		ret = pmbus_read_word_data(client, page, 0xff,
15762306a36Sopenharmony_ci					   PMBUS_VOUT_COMMAND);
15862306a36Sopenharmony_ci		if (ret < 0)
15962306a36Sopenharmony_ci			return ret;
16062306a36Sopenharmony_ci		offset = stpddc60_get_offset(ret, word, true);
16162306a36Sopenharmony_ci		ret = pmbus_write_byte_data(client, page,
16262306a36Sopenharmony_ci					    STPDDC60_MFR_OV_LIMIT_OFFSET,
16362306a36Sopenharmony_ci					    offset);
16462306a36Sopenharmony_ci		break;
16562306a36Sopenharmony_ci	case PMBUS_VOUT_UV_FAULT_LIMIT:
16662306a36Sopenharmony_ci		ret = pmbus_read_word_data(client, page, 0xff,
16762306a36Sopenharmony_ci					   PMBUS_VOUT_COMMAND);
16862306a36Sopenharmony_ci		if (ret < 0)
16962306a36Sopenharmony_ci			return ret;
17062306a36Sopenharmony_ci		offset = stpddc60_get_offset(ret, word, false);
17162306a36Sopenharmony_ci		ret = pmbus_write_byte_data(client, page,
17262306a36Sopenharmony_ci					    STPDDC60_MFR_UV_LIMIT_OFFSET,
17362306a36Sopenharmony_ci					    offset);
17462306a36Sopenharmony_ci		break;
17562306a36Sopenharmony_ci	case PMBUS_VIN_OV_FAULT_LIMIT:
17662306a36Sopenharmony_ci	case PMBUS_VIN_UV_FAULT_LIMIT:
17762306a36Sopenharmony_ci	case PMBUS_OT_FAULT_LIMIT:
17862306a36Sopenharmony_ci	case PMBUS_OT_WARN_LIMIT:
17962306a36Sopenharmony_ci	case PMBUS_IOUT_OC_FAULT_LIMIT:
18062306a36Sopenharmony_ci	case PMBUS_IOUT_OC_WARN_LIMIT:
18162306a36Sopenharmony_ci	case PMBUS_POUT_OP_FAULT_LIMIT:
18262306a36Sopenharmony_ci		ret = pmbus_read_word_data(client, page, 0xff, reg);
18362306a36Sopenharmony_ci		if (ret < 0)
18462306a36Sopenharmony_ci			return ret;
18562306a36Sopenharmony_ci		word = stpddc60_adjust_linear(word, ret >> 11);
18662306a36Sopenharmony_ci		ret = pmbus_write_word_data(client, page, reg, word);
18762306a36Sopenharmony_ci		break;
18862306a36Sopenharmony_ci	default:
18962306a36Sopenharmony_ci		ret = -ENODATA;
19062306a36Sopenharmony_ci		break;
19162306a36Sopenharmony_ci	}
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	return ret;
19462306a36Sopenharmony_ci}
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_cistatic int stpddc60_probe(struct i2c_client *client)
19762306a36Sopenharmony_ci{
19862306a36Sopenharmony_ci	int status;
19962306a36Sopenharmony_ci	u8 device_id[I2C_SMBUS_BLOCK_MAX + 1];
20062306a36Sopenharmony_ci	const struct i2c_device_id *mid;
20162306a36Sopenharmony_ci	struct pmbus_driver_info *info = &stpddc60_info;
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	if (!i2c_check_functionality(client->adapter,
20462306a36Sopenharmony_ci				     I2C_FUNC_SMBUS_READ_BYTE_DATA
20562306a36Sopenharmony_ci				     | I2C_FUNC_SMBUS_BLOCK_DATA))
20662306a36Sopenharmony_ci		return -ENODEV;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	status = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, device_id);
20962306a36Sopenharmony_ci	if (status < 0) {
21062306a36Sopenharmony_ci		dev_err(&client->dev, "Failed to read Manufacturer Model\n");
21162306a36Sopenharmony_ci		return status;
21262306a36Sopenharmony_ci	}
21362306a36Sopenharmony_ci	for (mid = stpddc60_id; mid->name[0]; mid++) {
21462306a36Sopenharmony_ci		if (!strncasecmp(mid->name, device_id, strlen(mid->name)))
21562306a36Sopenharmony_ci			break;
21662306a36Sopenharmony_ci	}
21762306a36Sopenharmony_ci	if (!mid->name[0]) {
21862306a36Sopenharmony_ci		dev_err(&client->dev, "Unsupported device\n");
21962306a36Sopenharmony_ci		return -ENODEV;
22062306a36Sopenharmony_ci	}
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	info->read_byte_data = stpddc60_read_byte_data;
22362306a36Sopenharmony_ci	info->read_word_data = stpddc60_read_word_data;
22462306a36Sopenharmony_ci	info->write_word_data = stpddc60_write_word_data;
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci	status = pmbus_do_probe(client, info);
22762306a36Sopenharmony_ci	if (status < 0)
22862306a36Sopenharmony_ci		return status;
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	pmbus_set_update(client, PMBUS_VOUT_OV_FAULT_LIMIT, true);
23162306a36Sopenharmony_ci	pmbus_set_update(client, PMBUS_VOUT_UV_FAULT_LIMIT, true);
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci	return 0;
23462306a36Sopenharmony_ci}
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_cistatic struct i2c_driver stpddc60_driver = {
23762306a36Sopenharmony_ci	.driver = {
23862306a36Sopenharmony_ci		   .name = "stpddc60",
23962306a36Sopenharmony_ci		   },
24062306a36Sopenharmony_ci	.probe = stpddc60_probe,
24162306a36Sopenharmony_ci	.id_table = stpddc60_id,
24262306a36Sopenharmony_ci};
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_cimodule_i2c_driver(stpddc60_driver);
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ciMODULE_AUTHOR("Erik Rosen <erik.rosen@metormote.com>");
24762306a36Sopenharmony_ciMODULE_DESCRIPTION("PMBus driver for ST STPDDC60");
24862306a36Sopenharmony_ciMODULE_LICENSE("GPL");
24962306a36Sopenharmony_ciMODULE_IMPORT_NS(PMBUS);
250