162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Hardware monitoring driver for PMBus devices
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (c) 2010, 2011 Ericsson 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/slab.h>
1362306a36Sopenharmony_ci#include <linux/mutex.h>
1462306a36Sopenharmony_ci#include <linux/i2c.h>
1562306a36Sopenharmony_ci#include <linux/pmbus.h>
1662306a36Sopenharmony_ci#include "pmbus.h"
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_cistruct pmbus_device_info {
1962306a36Sopenharmony_ci	int pages;
2062306a36Sopenharmony_ci	u32 flags;
2162306a36Sopenharmony_ci};
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_cistatic const struct i2c_device_id pmbus_id[];
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci/*
2662306a36Sopenharmony_ci * Find sensor groups and status registers on each page.
2762306a36Sopenharmony_ci */
2862306a36Sopenharmony_cistatic void pmbus_find_sensor_groups(struct i2c_client *client,
2962306a36Sopenharmony_ci				     struct pmbus_driver_info *info)
3062306a36Sopenharmony_ci{
3162306a36Sopenharmony_ci	int page;
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci	/* Sensors detected on page 0 only */
3462306a36Sopenharmony_ci	if (pmbus_check_word_register(client, 0, PMBUS_READ_VIN))
3562306a36Sopenharmony_ci		info->func[0] |= PMBUS_HAVE_VIN;
3662306a36Sopenharmony_ci	if (pmbus_check_word_register(client, 0, PMBUS_READ_VCAP))
3762306a36Sopenharmony_ci		info->func[0] |= PMBUS_HAVE_VCAP;
3862306a36Sopenharmony_ci	if (pmbus_check_word_register(client, 0, PMBUS_READ_IIN))
3962306a36Sopenharmony_ci		info->func[0] |= PMBUS_HAVE_IIN;
4062306a36Sopenharmony_ci	if (pmbus_check_word_register(client, 0, PMBUS_READ_PIN))
4162306a36Sopenharmony_ci		info->func[0] |= PMBUS_HAVE_PIN;
4262306a36Sopenharmony_ci	if (info->func[0]
4362306a36Sopenharmony_ci	    && pmbus_check_byte_register(client, 0, PMBUS_STATUS_INPUT))
4462306a36Sopenharmony_ci		info->func[0] |= PMBUS_HAVE_STATUS_INPUT;
4562306a36Sopenharmony_ci	if (pmbus_check_byte_register(client, 0, PMBUS_FAN_CONFIG_12) &&
4662306a36Sopenharmony_ci	    pmbus_check_word_register(client, 0, PMBUS_READ_FAN_SPEED_1)) {
4762306a36Sopenharmony_ci		info->func[0] |= PMBUS_HAVE_FAN12;
4862306a36Sopenharmony_ci		if (pmbus_check_byte_register(client, 0, PMBUS_STATUS_FAN_12))
4962306a36Sopenharmony_ci			info->func[0] |= PMBUS_HAVE_STATUS_FAN12;
5062306a36Sopenharmony_ci	}
5162306a36Sopenharmony_ci	if (pmbus_check_byte_register(client, 0, PMBUS_FAN_CONFIG_34) &&
5262306a36Sopenharmony_ci	    pmbus_check_word_register(client, 0, PMBUS_READ_FAN_SPEED_3)) {
5362306a36Sopenharmony_ci		info->func[0] |= PMBUS_HAVE_FAN34;
5462306a36Sopenharmony_ci		if (pmbus_check_byte_register(client, 0, PMBUS_STATUS_FAN_34))
5562306a36Sopenharmony_ci			info->func[0] |= PMBUS_HAVE_STATUS_FAN34;
5662306a36Sopenharmony_ci	}
5762306a36Sopenharmony_ci	if (pmbus_check_word_register(client, 0, PMBUS_READ_TEMPERATURE_1))
5862306a36Sopenharmony_ci		info->func[0] |= PMBUS_HAVE_TEMP;
5962306a36Sopenharmony_ci	if (pmbus_check_word_register(client, 0, PMBUS_READ_TEMPERATURE_2))
6062306a36Sopenharmony_ci		info->func[0] |= PMBUS_HAVE_TEMP2;
6162306a36Sopenharmony_ci	if (pmbus_check_word_register(client, 0, PMBUS_READ_TEMPERATURE_3))
6262306a36Sopenharmony_ci		info->func[0] |= PMBUS_HAVE_TEMP3;
6362306a36Sopenharmony_ci	if (info->func[0] & (PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2
6462306a36Sopenharmony_ci			     | PMBUS_HAVE_TEMP3)
6562306a36Sopenharmony_ci	    && pmbus_check_byte_register(client, 0,
6662306a36Sopenharmony_ci					 PMBUS_STATUS_TEMPERATURE))
6762306a36Sopenharmony_ci			info->func[0] |= PMBUS_HAVE_STATUS_TEMP;
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	/* Sensors detected on all pages */
7062306a36Sopenharmony_ci	for (page = 0; page < info->pages; page++) {
7162306a36Sopenharmony_ci		if (pmbus_check_word_register(client, page, PMBUS_READ_VOUT)) {
7262306a36Sopenharmony_ci			info->func[page] |= PMBUS_HAVE_VOUT;
7362306a36Sopenharmony_ci			if (pmbus_check_byte_register(client, page,
7462306a36Sopenharmony_ci						      PMBUS_STATUS_VOUT))
7562306a36Sopenharmony_ci				info->func[page] |= PMBUS_HAVE_STATUS_VOUT;
7662306a36Sopenharmony_ci		}
7762306a36Sopenharmony_ci		if (pmbus_check_word_register(client, page, PMBUS_READ_IOUT)) {
7862306a36Sopenharmony_ci			info->func[page] |= PMBUS_HAVE_IOUT;
7962306a36Sopenharmony_ci			if (pmbus_check_byte_register(client, 0,
8062306a36Sopenharmony_ci						      PMBUS_STATUS_IOUT))
8162306a36Sopenharmony_ci				info->func[page] |= PMBUS_HAVE_STATUS_IOUT;
8262306a36Sopenharmony_ci		}
8362306a36Sopenharmony_ci		if (pmbus_check_word_register(client, page, PMBUS_READ_POUT))
8462306a36Sopenharmony_ci			info->func[page] |= PMBUS_HAVE_POUT;
8562306a36Sopenharmony_ci	}
8662306a36Sopenharmony_ci}
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci/*
8962306a36Sopenharmony_ci * Identify chip parameters.
9062306a36Sopenharmony_ci */
9162306a36Sopenharmony_cistatic int pmbus_identify(struct i2c_client *client,
9262306a36Sopenharmony_ci			  struct pmbus_driver_info *info)
9362306a36Sopenharmony_ci{
9462306a36Sopenharmony_ci	int ret = 0;
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	if (!info->pages) {
9762306a36Sopenharmony_ci		/*
9862306a36Sopenharmony_ci		 * Check if the PAGE command is supported. If it is,
9962306a36Sopenharmony_ci		 * keep setting the page number until it fails or until the
10062306a36Sopenharmony_ci		 * maximum number of pages has been reached. Assume that
10162306a36Sopenharmony_ci		 * this is the number of pages supported by the chip.
10262306a36Sopenharmony_ci		 */
10362306a36Sopenharmony_ci		if (pmbus_check_byte_register(client, 0, PMBUS_PAGE)) {
10462306a36Sopenharmony_ci			int page;
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci			for (page = 1; page < PMBUS_PAGES; page++) {
10762306a36Sopenharmony_ci				if (pmbus_set_page(client, page, 0xff) < 0)
10862306a36Sopenharmony_ci					break;
10962306a36Sopenharmony_ci			}
11062306a36Sopenharmony_ci			pmbus_set_page(client, 0, 0xff);
11162306a36Sopenharmony_ci			info->pages = page;
11262306a36Sopenharmony_ci		} else {
11362306a36Sopenharmony_ci			info->pages = 1;
11462306a36Sopenharmony_ci		}
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci		pmbus_clear_faults(client);
11762306a36Sopenharmony_ci	}
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	if (pmbus_check_byte_register(client, 0, PMBUS_VOUT_MODE)) {
12062306a36Sopenharmony_ci		int vout_mode, i;
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci		vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE);
12362306a36Sopenharmony_ci		if (vout_mode >= 0 && vout_mode != 0xff) {
12462306a36Sopenharmony_ci			switch (vout_mode >> 5) {
12562306a36Sopenharmony_ci			case 0:
12662306a36Sopenharmony_ci				break;
12762306a36Sopenharmony_ci			case 1:
12862306a36Sopenharmony_ci				info->format[PSC_VOLTAGE_OUT] = vid;
12962306a36Sopenharmony_ci				for (i = 0; i < info->pages; i++)
13062306a36Sopenharmony_ci					info->vrm_version[i] = vr11;
13162306a36Sopenharmony_ci				break;
13262306a36Sopenharmony_ci			case 2:
13362306a36Sopenharmony_ci				info->format[PSC_VOLTAGE_OUT] = direct;
13462306a36Sopenharmony_ci				break;
13562306a36Sopenharmony_ci			default:
13662306a36Sopenharmony_ci				ret = -ENODEV;
13762306a36Sopenharmony_ci				goto abort;
13862306a36Sopenharmony_ci			}
13962306a36Sopenharmony_ci		}
14062306a36Sopenharmony_ci	}
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	/*
14362306a36Sopenharmony_ci	 * We should check if the COEFFICIENTS register is supported.
14462306a36Sopenharmony_ci	 * If it is, and the chip is configured for direct mode, we can read
14562306a36Sopenharmony_ci	 * the coefficients from the chip, one set per group of sensor
14662306a36Sopenharmony_ci	 * registers.
14762306a36Sopenharmony_ci	 *
14862306a36Sopenharmony_ci	 * To do this, we will need access to a chip which actually supports the
14962306a36Sopenharmony_ci	 * COEFFICIENTS command, since the command is too complex to implement
15062306a36Sopenharmony_ci	 * without testing it. Until then, abort if a chip configured for direct
15162306a36Sopenharmony_ci	 * mode was detected.
15262306a36Sopenharmony_ci	 */
15362306a36Sopenharmony_ci	if (info->format[PSC_VOLTAGE_OUT] == direct) {
15462306a36Sopenharmony_ci		ret = -ENODEV;
15562306a36Sopenharmony_ci		goto abort;
15662306a36Sopenharmony_ci	}
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	/* Try to find sensor groups  */
15962306a36Sopenharmony_ci	pmbus_find_sensor_groups(client, info);
16062306a36Sopenharmony_ciabort:
16162306a36Sopenharmony_ci	return ret;
16262306a36Sopenharmony_ci}
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_cistatic int pmbus_probe(struct i2c_client *client)
16562306a36Sopenharmony_ci{
16662306a36Sopenharmony_ci	struct pmbus_driver_info *info;
16762306a36Sopenharmony_ci	struct pmbus_platform_data *pdata = NULL;
16862306a36Sopenharmony_ci	struct device *dev = &client->dev;
16962306a36Sopenharmony_ci	struct pmbus_device_info *device_info;
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	info = devm_kzalloc(dev, sizeof(struct pmbus_driver_info), GFP_KERNEL);
17262306a36Sopenharmony_ci	if (!info)
17362306a36Sopenharmony_ci		return -ENOMEM;
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	device_info = (struct pmbus_device_info *)i2c_match_id(pmbus_id, client)->driver_data;
17662306a36Sopenharmony_ci	if (device_info->flags) {
17762306a36Sopenharmony_ci		pdata = devm_kzalloc(dev, sizeof(struct pmbus_platform_data),
17862306a36Sopenharmony_ci				     GFP_KERNEL);
17962306a36Sopenharmony_ci		if (!pdata)
18062306a36Sopenharmony_ci			return -ENOMEM;
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci		pdata->flags = device_info->flags;
18362306a36Sopenharmony_ci	}
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	info->pages = device_info->pages;
18662306a36Sopenharmony_ci	info->identify = pmbus_identify;
18762306a36Sopenharmony_ci	dev->platform_data = pdata;
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	return pmbus_do_probe(client, info);
19062306a36Sopenharmony_ci}
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_cistatic const struct pmbus_device_info pmbus_info_one = {
19362306a36Sopenharmony_ci	.pages = 1,
19462306a36Sopenharmony_ci	.flags = 0
19562306a36Sopenharmony_ci};
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_cistatic const struct pmbus_device_info pmbus_info_zero = {
19862306a36Sopenharmony_ci	.pages = 0,
19962306a36Sopenharmony_ci	.flags = 0
20062306a36Sopenharmony_ci};
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_cistatic const struct pmbus_device_info pmbus_info_one_skip = {
20362306a36Sopenharmony_ci	.pages = 1,
20462306a36Sopenharmony_ci	.flags = PMBUS_SKIP_STATUS_CHECK
20562306a36Sopenharmony_ci};
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_cistatic const struct pmbus_device_info pmbus_info_one_status = {
20862306a36Sopenharmony_ci	.pages = 1,
20962306a36Sopenharmony_ci	.flags = PMBUS_READ_STATUS_AFTER_FAILED_CHECK
21062306a36Sopenharmony_ci};
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci/*
21362306a36Sopenharmony_ci * Use driver_data to set the number of pages supported by the chip.
21462306a36Sopenharmony_ci */
21562306a36Sopenharmony_cistatic const struct i2c_device_id pmbus_id[] = {
21662306a36Sopenharmony_ci	{"adp4000", (kernel_ulong_t)&pmbus_info_one},
21762306a36Sopenharmony_ci	{"bmr310", (kernel_ulong_t)&pmbus_info_one_status},
21862306a36Sopenharmony_ci	{"bmr453", (kernel_ulong_t)&pmbus_info_one},
21962306a36Sopenharmony_ci	{"bmr454", (kernel_ulong_t)&pmbus_info_one},
22062306a36Sopenharmony_ci	{"bmr456", (kernel_ulong_t)&pmbus_info_one},
22162306a36Sopenharmony_ci	{"bmr457", (kernel_ulong_t)&pmbus_info_one},
22262306a36Sopenharmony_ci	{"bmr458", (kernel_ulong_t)&pmbus_info_one_status},
22362306a36Sopenharmony_ci	{"bmr480", (kernel_ulong_t)&pmbus_info_one_status},
22462306a36Sopenharmony_ci	{"bmr490", (kernel_ulong_t)&pmbus_info_one_status},
22562306a36Sopenharmony_ci	{"bmr491", (kernel_ulong_t)&pmbus_info_one_status},
22662306a36Sopenharmony_ci	{"bmr492", (kernel_ulong_t)&pmbus_info_one},
22762306a36Sopenharmony_ci	{"dps460", (kernel_ulong_t)&pmbus_info_one_skip},
22862306a36Sopenharmony_ci	{"dps650ab", (kernel_ulong_t)&pmbus_info_one_skip},
22962306a36Sopenharmony_ci	{"dps800", (kernel_ulong_t)&pmbus_info_one_skip},
23062306a36Sopenharmony_ci	{"max20796", (kernel_ulong_t)&pmbus_info_one},
23162306a36Sopenharmony_ci	{"mdt040", (kernel_ulong_t)&pmbus_info_one},
23262306a36Sopenharmony_ci	{"ncp4200", (kernel_ulong_t)&pmbus_info_one},
23362306a36Sopenharmony_ci	{"ncp4208", (kernel_ulong_t)&pmbus_info_one},
23462306a36Sopenharmony_ci	{"pdt003", (kernel_ulong_t)&pmbus_info_one},
23562306a36Sopenharmony_ci	{"pdt006", (kernel_ulong_t)&pmbus_info_one},
23662306a36Sopenharmony_ci	{"pdt012", (kernel_ulong_t)&pmbus_info_one},
23762306a36Sopenharmony_ci	{"pmbus", (kernel_ulong_t)&pmbus_info_zero},
23862306a36Sopenharmony_ci	{"sgd009", (kernel_ulong_t)&pmbus_info_one_skip},
23962306a36Sopenharmony_ci	{"tps40400", (kernel_ulong_t)&pmbus_info_one},
24062306a36Sopenharmony_ci	{"tps544b20", (kernel_ulong_t)&pmbus_info_one},
24162306a36Sopenharmony_ci	{"tps544b25", (kernel_ulong_t)&pmbus_info_one},
24262306a36Sopenharmony_ci	{"tps544c20", (kernel_ulong_t)&pmbus_info_one},
24362306a36Sopenharmony_ci	{"tps544c25", (kernel_ulong_t)&pmbus_info_one},
24462306a36Sopenharmony_ci	{"udt020", (kernel_ulong_t)&pmbus_info_one},
24562306a36Sopenharmony_ci	{}
24662306a36Sopenharmony_ci};
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, pmbus_id);
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci/* This is the driver that will be inserted */
25162306a36Sopenharmony_cistatic struct i2c_driver pmbus_driver = {
25262306a36Sopenharmony_ci	.driver = {
25362306a36Sopenharmony_ci		   .name = "pmbus",
25462306a36Sopenharmony_ci		   },
25562306a36Sopenharmony_ci	.probe = pmbus_probe,
25662306a36Sopenharmony_ci	.id_table = pmbus_id,
25762306a36Sopenharmony_ci};
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_cimodule_i2c_driver(pmbus_driver);
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ciMODULE_AUTHOR("Guenter Roeck");
26262306a36Sopenharmony_ciMODULE_DESCRIPTION("Generic PMBus driver");
26362306a36Sopenharmony_ciMODULE_LICENSE("GPL");
26462306a36Sopenharmony_ciMODULE_IMPORT_NS(PMBUS);
265