162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Hardware monitoring driver for PIM4006, PIM4328 and PIM4820
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (c) 2021 Flextronics International Sweden AB
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/err.h>
962306a36Sopenharmony_ci#include <linux/i2c.h>
1062306a36Sopenharmony_ci#include <linux/init.h>
1162306a36Sopenharmony_ci#include <linux/kernel.h>
1262306a36Sopenharmony_ci#include <linux/module.h>
1362306a36Sopenharmony_ci#include <linux/pmbus.h>
1462306a36Sopenharmony_ci#include <linux/slab.h>
1562306a36Sopenharmony_ci#include "pmbus.h"
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_cienum chips { pim4006, pim4328, pim4820 };
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_cistruct pim4328_data {
2062306a36Sopenharmony_ci	enum chips id;
2162306a36Sopenharmony_ci	struct pmbus_driver_info info;
2262306a36Sopenharmony_ci};
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#define to_pim4328_data(x)  container_of(x, struct pim4328_data, info)
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci/* PIM4006 and PIM4328 */
2762306a36Sopenharmony_ci#define PIM4328_MFR_READ_VINA		0xd3
2862306a36Sopenharmony_ci#define PIM4328_MFR_READ_VINB		0xd4
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci/* PIM4006 */
3162306a36Sopenharmony_ci#define PIM4328_MFR_READ_IINA		0xd6
3262306a36Sopenharmony_ci#define PIM4328_MFR_READ_IINB		0xd7
3362306a36Sopenharmony_ci#define PIM4328_MFR_FET_CHECKSTATUS	0xd9
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci/* PIM4328 */
3662306a36Sopenharmony_ci#define PIM4328_MFR_STATUS_BITS		0xd5
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci/* PIM4820 */
3962306a36Sopenharmony_ci#define PIM4328_MFR_READ_STATUS		0xd0
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_cistatic const struct i2c_device_id pim4328_id[] = {
4262306a36Sopenharmony_ci	{"bmr455", pim4328},
4362306a36Sopenharmony_ci	{"pim4006", pim4006},
4462306a36Sopenharmony_ci	{"pim4106", pim4006},
4562306a36Sopenharmony_ci	{"pim4206", pim4006},
4662306a36Sopenharmony_ci	{"pim4306", pim4006},
4762306a36Sopenharmony_ci	{"pim4328", pim4328},
4862306a36Sopenharmony_ci	{"pim4406", pim4006},
4962306a36Sopenharmony_ci	{"pim4820", pim4820},
5062306a36Sopenharmony_ci	{}
5162306a36Sopenharmony_ci};
5262306a36Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, pim4328_id);
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_cistatic int pim4328_read_word_data(struct i2c_client *client, int page,
5562306a36Sopenharmony_ci				  int phase, int reg)
5662306a36Sopenharmony_ci{
5762306a36Sopenharmony_ci	int ret;
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	if (page > 0)
6062306a36Sopenharmony_ci		return -ENXIO;
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	if (phase == 0xff)
6362306a36Sopenharmony_ci		return -ENODATA;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	switch (reg) {
6662306a36Sopenharmony_ci	case PMBUS_READ_VIN:
6762306a36Sopenharmony_ci		ret = pmbus_read_word_data(client, page, phase,
6862306a36Sopenharmony_ci					   phase == 0 ? PIM4328_MFR_READ_VINA
6962306a36Sopenharmony_ci						      : PIM4328_MFR_READ_VINB);
7062306a36Sopenharmony_ci		break;
7162306a36Sopenharmony_ci	case PMBUS_READ_IIN:
7262306a36Sopenharmony_ci		ret = pmbus_read_word_data(client, page, phase,
7362306a36Sopenharmony_ci					   phase == 0 ? PIM4328_MFR_READ_IINA
7462306a36Sopenharmony_ci						      : PIM4328_MFR_READ_IINB);
7562306a36Sopenharmony_ci		break;
7662306a36Sopenharmony_ci	default:
7762306a36Sopenharmony_ci		ret = -ENODATA;
7862306a36Sopenharmony_ci	}
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	return ret;
8162306a36Sopenharmony_ci}
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_cistatic int pim4328_read_byte_data(struct i2c_client *client, int page, int reg)
8462306a36Sopenharmony_ci{
8562306a36Sopenharmony_ci	const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
8662306a36Sopenharmony_ci	struct pim4328_data *data = to_pim4328_data(info);
8762306a36Sopenharmony_ci	int ret, status;
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	if (page > 0)
9062306a36Sopenharmony_ci		return -ENXIO;
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	switch (reg) {
9362306a36Sopenharmony_ci	case PMBUS_STATUS_BYTE:
9462306a36Sopenharmony_ci		ret = pmbus_read_byte_data(client, page, PMBUS_STATUS_BYTE);
9562306a36Sopenharmony_ci		if (ret < 0)
9662306a36Sopenharmony_ci			return ret;
9762306a36Sopenharmony_ci		if (data->id == pim4006) {
9862306a36Sopenharmony_ci			status = pmbus_read_word_data(client, page, 0xff,
9962306a36Sopenharmony_ci						      PIM4328_MFR_FET_CHECKSTATUS);
10062306a36Sopenharmony_ci			if (status < 0)
10162306a36Sopenharmony_ci				return status;
10262306a36Sopenharmony_ci			if (status & 0x0630) /* Input UV */
10362306a36Sopenharmony_ci				ret |= PB_STATUS_VIN_UV;
10462306a36Sopenharmony_ci		} else if (data->id == pim4328) {
10562306a36Sopenharmony_ci			status = pmbus_read_byte_data(client, page,
10662306a36Sopenharmony_ci						      PIM4328_MFR_STATUS_BITS);
10762306a36Sopenharmony_ci			if (status < 0)
10862306a36Sopenharmony_ci				return status;
10962306a36Sopenharmony_ci			if (status & 0x04) /* Input UV */
11062306a36Sopenharmony_ci				ret |= PB_STATUS_VIN_UV;
11162306a36Sopenharmony_ci			if (status & 0x40) /* Output UV */
11262306a36Sopenharmony_ci				ret |= PB_STATUS_NONE_ABOVE;
11362306a36Sopenharmony_ci		} else if (data->id == pim4820) {
11462306a36Sopenharmony_ci			status = pmbus_read_byte_data(client, page,
11562306a36Sopenharmony_ci						      PIM4328_MFR_READ_STATUS);
11662306a36Sopenharmony_ci			if (status < 0)
11762306a36Sopenharmony_ci				return status;
11862306a36Sopenharmony_ci			if (status & 0x05) /* Input OV or OC */
11962306a36Sopenharmony_ci				ret |= PB_STATUS_NONE_ABOVE;
12062306a36Sopenharmony_ci			if (status & 0x1a) /* Input UV */
12162306a36Sopenharmony_ci				ret |= PB_STATUS_VIN_UV;
12262306a36Sopenharmony_ci			if (status & 0x40) /* OT */
12362306a36Sopenharmony_ci				ret |= PB_STATUS_TEMPERATURE;
12462306a36Sopenharmony_ci		}
12562306a36Sopenharmony_ci		break;
12662306a36Sopenharmony_ci	default:
12762306a36Sopenharmony_ci		ret = -ENODATA;
12862306a36Sopenharmony_ci	}
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	return ret;
13162306a36Sopenharmony_ci}
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_cistatic int pim4328_probe(struct i2c_client *client)
13462306a36Sopenharmony_ci{
13562306a36Sopenharmony_ci	int status;
13662306a36Sopenharmony_ci	u8 device_id[I2C_SMBUS_BLOCK_MAX + 1];
13762306a36Sopenharmony_ci	const struct i2c_device_id *mid;
13862306a36Sopenharmony_ci	struct pim4328_data *data;
13962306a36Sopenharmony_ci	struct pmbus_driver_info *info;
14062306a36Sopenharmony_ci	struct pmbus_platform_data *pdata;
14162306a36Sopenharmony_ci	struct device *dev = &client->dev;
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci	if (!i2c_check_functionality(client->adapter,
14462306a36Sopenharmony_ci				     I2C_FUNC_SMBUS_READ_BYTE_DATA
14562306a36Sopenharmony_ci				     | I2C_FUNC_SMBUS_BLOCK_DATA))
14662306a36Sopenharmony_ci		return -ENODEV;
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	data = devm_kzalloc(&client->dev, sizeof(struct pim4328_data),
14962306a36Sopenharmony_ci			    GFP_KERNEL);
15062306a36Sopenharmony_ci	if (!data)
15162306a36Sopenharmony_ci		return -ENOMEM;
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	status = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, device_id);
15462306a36Sopenharmony_ci	if (status < 0) {
15562306a36Sopenharmony_ci		dev_err(&client->dev, "Failed to read Manufacturer Model\n");
15662306a36Sopenharmony_ci		return status;
15762306a36Sopenharmony_ci	}
15862306a36Sopenharmony_ci	for (mid = pim4328_id; mid->name[0]; mid++) {
15962306a36Sopenharmony_ci		if (!strncasecmp(mid->name, device_id, strlen(mid->name)))
16062306a36Sopenharmony_ci			break;
16162306a36Sopenharmony_ci	}
16262306a36Sopenharmony_ci	if (!mid->name[0]) {
16362306a36Sopenharmony_ci		dev_err(&client->dev, "Unsupported device\n");
16462306a36Sopenharmony_ci		return -ENODEV;
16562306a36Sopenharmony_ci	}
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	if (strcmp(client->name, mid->name))
16862306a36Sopenharmony_ci		dev_notice(&client->dev,
16962306a36Sopenharmony_ci			   "Device mismatch: Configured %s, detected %s\n",
17062306a36Sopenharmony_ci			   client->name, mid->name);
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci	data->id = mid->driver_data;
17362306a36Sopenharmony_ci	info = &data->info;
17462306a36Sopenharmony_ci	info->pages = 1;
17562306a36Sopenharmony_ci	info->read_byte_data = pim4328_read_byte_data;
17662306a36Sopenharmony_ci	info->read_word_data = pim4328_read_word_data;
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci	pdata = devm_kzalloc(dev, sizeof(struct pmbus_platform_data),
17962306a36Sopenharmony_ci			     GFP_KERNEL);
18062306a36Sopenharmony_ci	if (!pdata)
18162306a36Sopenharmony_ci		return -ENOMEM;
18262306a36Sopenharmony_ci	dev->platform_data = pdata;
18362306a36Sopenharmony_ci	pdata->flags = PMBUS_NO_CAPABILITY | PMBUS_NO_WRITE_PROTECT;
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	switch (data->id) {
18662306a36Sopenharmony_ci	case pim4006:
18762306a36Sopenharmony_ci		info->phases[0] = 2;
18862306a36Sopenharmony_ci		info->func[0] = PMBUS_PHASE_VIRTUAL | PMBUS_HAVE_VIN
18962306a36Sopenharmony_ci			| PMBUS_HAVE_TEMP | PMBUS_HAVE_IOUT;
19062306a36Sopenharmony_ci		info->pfunc[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN;
19162306a36Sopenharmony_ci		info->pfunc[1] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN;
19262306a36Sopenharmony_ci		break;
19362306a36Sopenharmony_ci	case pim4328:
19462306a36Sopenharmony_ci		info->phases[0] = 2;
19562306a36Sopenharmony_ci		info->func[0] = PMBUS_PHASE_VIRTUAL
19662306a36Sopenharmony_ci			| PMBUS_HAVE_VCAP | PMBUS_HAVE_VIN
19762306a36Sopenharmony_ci			| PMBUS_HAVE_TEMP | PMBUS_HAVE_IOUT;
19862306a36Sopenharmony_ci		info->pfunc[0] = PMBUS_HAVE_VIN;
19962306a36Sopenharmony_ci		info->pfunc[1] = PMBUS_HAVE_VIN;
20062306a36Sopenharmony_ci		info->format[PSC_VOLTAGE_IN] = direct;
20162306a36Sopenharmony_ci		info->format[PSC_TEMPERATURE] = direct;
20262306a36Sopenharmony_ci		info->format[PSC_CURRENT_OUT] = direct;
20362306a36Sopenharmony_ci		pdata->flags |= PMBUS_USE_COEFFICIENTS_CMD;
20462306a36Sopenharmony_ci		break;
20562306a36Sopenharmony_ci	case pim4820:
20662306a36Sopenharmony_ci		info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_TEMP
20762306a36Sopenharmony_ci			| PMBUS_HAVE_IIN;
20862306a36Sopenharmony_ci		info->format[PSC_VOLTAGE_IN] = direct;
20962306a36Sopenharmony_ci		info->format[PSC_TEMPERATURE] = direct;
21062306a36Sopenharmony_ci		info->format[PSC_CURRENT_IN] = direct;
21162306a36Sopenharmony_ci		pdata->flags |= PMBUS_USE_COEFFICIENTS_CMD;
21262306a36Sopenharmony_ci		break;
21362306a36Sopenharmony_ci	default:
21462306a36Sopenharmony_ci		return -ENODEV;
21562306a36Sopenharmony_ci	}
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci	return pmbus_do_probe(client, info);
21862306a36Sopenharmony_ci}
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_cistatic struct i2c_driver pim4328_driver = {
22162306a36Sopenharmony_ci	.driver = {
22262306a36Sopenharmony_ci		   .name = "pim4328",
22362306a36Sopenharmony_ci		   },
22462306a36Sopenharmony_ci	.probe = pim4328_probe,
22562306a36Sopenharmony_ci	.id_table = pim4328_id,
22662306a36Sopenharmony_ci};
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_cimodule_i2c_driver(pim4328_driver);
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ciMODULE_AUTHOR("Erik Rosen <erik.rosen@metormote.com>");
23162306a36Sopenharmony_ciMODULE_DESCRIPTION("PMBus driver for PIM4006, PIM4328, PIM4820 power interface modules");
23262306a36Sopenharmony_ciMODULE_LICENSE("GPL");
23362306a36Sopenharmony_ciMODULE_IMPORT_NS(PMBUS);
234