18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Hardware monitoring driver for PMBus devices
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (c) 2010, 2011 Ericsson AB.
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/kernel.h>
98c2ecf20Sopenharmony_ci#include <linux/module.h>
108c2ecf20Sopenharmony_ci#include <linux/init.h>
118c2ecf20Sopenharmony_ci#include <linux/err.h>
128c2ecf20Sopenharmony_ci#include <linux/slab.h>
138c2ecf20Sopenharmony_ci#include <linux/mutex.h>
148c2ecf20Sopenharmony_ci#include <linux/i2c.h>
158c2ecf20Sopenharmony_ci#include <linux/pmbus.h>
168c2ecf20Sopenharmony_ci#include "pmbus.h"
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_cistruct pmbus_device_info {
198c2ecf20Sopenharmony_ci	int pages;
208c2ecf20Sopenharmony_ci	u32 flags;
218c2ecf20Sopenharmony_ci};
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_cistatic const struct i2c_device_id pmbus_id[];
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci/*
268c2ecf20Sopenharmony_ci * Find sensor groups and status registers on each page.
278c2ecf20Sopenharmony_ci */
288c2ecf20Sopenharmony_cistatic void pmbus_find_sensor_groups(struct i2c_client *client,
298c2ecf20Sopenharmony_ci				     struct pmbus_driver_info *info)
308c2ecf20Sopenharmony_ci{
318c2ecf20Sopenharmony_ci	int page;
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci	/* Sensors detected on page 0 only */
348c2ecf20Sopenharmony_ci	if (pmbus_check_word_register(client, 0, PMBUS_READ_VIN))
358c2ecf20Sopenharmony_ci		info->func[0] |= PMBUS_HAVE_VIN;
368c2ecf20Sopenharmony_ci	if (pmbus_check_word_register(client, 0, PMBUS_READ_VCAP))
378c2ecf20Sopenharmony_ci		info->func[0] |= PMBUS_HAVE_VCAP;
388c2ecf20Sopenharmony_ci	if (pmbus_check_word_register(client, 0, PMBUS_READ_IIN))
398c2ecf20Sopenharmony_ci		info->func[0] |= PMBUS_HAVE_IIN;
408c2ecf20Sopenharmony_ci	if (pmbus_check_word_register(client, 0, PMBUS_READ_PIN))
418c2ecf20Sopenharmony_ci		info->func[0] |= PMBUS_HAVE_PIN;
428c2ecf20Sopenharmony_ci	if (info->func[0]
438c2ecf20Sopenharmony_ci	    && pmbus_check_byte_register(client, 0, PMBUS_STATUS_INPUT))
448c2ecf20Sopenharmony_ci		info->func[0] |= PMBUS_HAVE_STATUS_INPUT;
458c2ecf20Sopenharmony_ci	if (pmbus_check_byte_register(client, 0, PMBUS_FAN_CONFIG_12) &&
468c2ecf20Sopenharmony_ci	    pmbus_check_word_register(client, 0, PMBUS_READ_FAN_SPEED_1)) {
478c2ecf20Sopenharmony_ci		info->func[0] |= PMBUS_HAVE_FAN12;
488c2ecf20Sopenharmony_ci		if (pmbus_check_byte_register(client, 0, PMBUS_STATUS_FAN_12))
498c2ecf20Sopenharmony_ci			info->func[0] |= PMBUS_HAVE_STATUS_FAN12;
508c2ecf20Sopenharmony_ci	}
518c2ecf20Sopenharmony_ci	if (pmbus_check_byte_register(client, 0, PMBUS_FAN_CONFIG_34) &&
528c2ecf20Sopenharmony_ci	    pmbus_check_word_register(client, 0, PMBUS_READ_FAN_SPEED_3)) {
538c2ecf20Sopenharmony_ci		info->func[0] |= PMBUS_HAVE_FAN34;
548c2ecf20Sopenharmony_ci		if (pmbus_check_byte_register(client, 0, PMBUS_STATUS_FAN_34))
558c2ecf20Sopenharmony_ci			info->func[0] |= PMBUS_HAVE_STATUS_FAN34;
568c2ecf20Sopenharmony_ci	}
578c2ecf20Sopenharmony_ci	if (pmbus_check_word_register(client, 0, PMBUS_READ_TEMPERATURE_1))
588c2ecf20Sopenharmony_ci		info->func[0] |= PMBUS_HAVE_TEMP;
598c2ecf20Sopenharmony_ci	if (pmbus_check_word_register(client, 0, PMBUS_READ_TEMPERATURE_2))
608c2ecf20Sopenharmony_ci		info->func[0] |= PMBUS_HAVE_TEMP2;
618c2ecf20Sopenharmony_ci	if (pmbus_check_word_register(client, 0, PMBUS_READ_TEMPERATURE_3))
628c2ecf20Sopenharmony_ci		info->func[0] |= PMBUS_HAVE_TEMP3;
638c2ecf20Sopenharmony_ci	if (info->func[0] & (PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2
648c2ecf20Sopenharmony_ci			     | PMBUS_HAVE_TEMP3)
658c2ecf20Sopenharmony_ci	    && pmbus_check_byte_register(client, 0,
668c2ecf20Sopenharmony_ci					 PMBUS_STATUS_TEMPERATURE))
678c2ecf20Sopenharmony_ci			info->func[0] |= PMBUS_HAVE_STATUS_TEMP;
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci	/* Sensors detected on all pages */
708c2ecf20Sopenharmony_ci	for (page = 0; page < info->pages; page++) {
718c2ecf20Sopenharmony_ci		if (pmbus_check_word_register(client, page, PMBUS_READ_VOUT)) {
728c2ecf20Sopenharmony_ci			info->func[page] |= PMBUS_HAVE_VOUT;
738c2ecf20Sopenharmony_ci			if (pmbus_check_byte_register(client, page,
748c2ecf20Sopenharmony_ci						      PMBUS_STATUS_VOUT))
758c2ecf20Sopenharmony_ci				info->func[page] |= PMBUS_HAVE_STATUS_VOUT;
768c2ecf20Sopenharmony_ci		}
778c2ecf20Sopenharmony_ci		if (pmbus_check_word_register(client, page, PMBUS_READ_IOUT)) {
788c2ecf20Sopenharmony_ci			info->func[page] |= PMBUS_HAVE_IOUT;
798c2ecf20Sopenharmony_ci			if (pmbus_check_byte_register(client, 0,
808c2ecf20Sopenharmony_ci						      PMBUS_STATUS_IOUT))
818c2ecf20Sopenharmony_ci				info->func[page] |= PMBUS_HAVE_STATUS_IOUT;
828c2ecf20Sopenharmony_ci		}
838c2ecf20Sopenharmony_ci		if (pmbus_check_word_register(client, page, PMBUS_READ_POUT))
848c2ecf20Sopenharmony_ci			info->func[page] |= PMBUS_HAVE_POUT;
858c2ecf20Sopenharmony_ci	}
868c2ecf20Sopenharmony_ci}
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci/*
898c2ecf20Sopenharmony_ci * Identify chip parameters.
908c2ecf20Sopenharmony_ci */
918c2ecf20Sopenharmony_cistatic int pmbus_identify(struct i2c_client *client,
928c2ecf20Sopenharmony_ci			  struct pmbus_driver_info *info)
938c2ecf20Sopenharmony_ci{
948c2ecf20Sopenharmony_ci	int ret = 0;
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	if (!info->pages) {
978c2ecf20Sopenharmony_ci		/*
988c2ecf20Sopenharmony_ci		 * Check if the PAGE command is supported. If it is,
998c2ecf20Sopenharmony_ci		 * keep setting the page number until it fails or until the
1008c2ecf20Sopenharmony_ci		 * maximum number of pages has been reached. Assume that
1018c2ecf20Sopenharmony_ci		 * this is the number of pages supported by the chip.
1028c2ecf20Sopenharmony_ci		 */
1038c2ecf20Sopenharmony_ci		if (pmbus_check_byte_register(client, 0, PMBUS_PAGE)) {
1048c2ecf20Sopenharmony_ci			int page;
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci			for (page = 1; page < PMBUS_PAGES; page++) {
1078c2ecf20Sopenharmony_ci				if (pmbus_set_page(client, page, 0xff) < 0)
1088c2ecf20Sopenharmony_ci					break;
1098c2ecf20Sopenharmony_ci			}
1108c2ecf20Sopenharmony_ci			pmbus_set_page(client, 0, 0xff);
1118c2ecf20Sopenharmony_ci			info->pages = page;
1128c2ecf20Sopenharmony_ci		} else {
1138c2ecf20Sopenharmony_ci			info->pages = 1;
1148c2ecf20Sopenharmony_ci		}
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci		pmbus_clear_faults(client);
1178c2ecf20Sopenharmony_ci	}
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci	if (pmbus_check_byte_register(client, 0, PMBUS_VOUT_MODE)) {
1208c2ecf20Sopenharmony_ci		int vout_mode, i;
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci		vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE);
1238c2ecf20Sopenharmony_ci		if (vout_mode >= 0 && vout_mode != 0xff) {
1248c2ecf20Sopenharmony_ci			switch (vout_mode >> 5) {
1258c2ecf20Sopenharmony_ci			case 0:
1268c2ecf20Sopenharmony_ci				break;
1278c2ecf20Sopenharmony_ci			case 1:
1288c2ecf20Sopenharmony_ci				info->format[PSC_VOLTAGE_OUT] = vid;
1298c2ecf20Sopenharmony_ci				for (i = 0; i < info->pages; i++)
1308c2ecf20Sopenharmony_ci					info->vrm_version[i] = vr11;
1318c2ecf20Sopenharmony_ci				break;
1328c2ecf20Sopenharmony_ci			case 2:
1338c2ecf20Sopenharmony_ci				info->format[PSC_VOLTAGE_OUT] = direct;
1348c2ecf20Sopenharmony_ci				break;
1358c2ecf20Sopenharmony_ci			default:
1368c2ecf20Sopenharmony_ci				ret = -ENODEV;
1378c2ecf20Sopenharmony_ci				goto abort;
1388c2ecf20Sopenharmony_ci			}
1398c2ecf20Sopenharmony_ci		}
1408c2ecf20Sopenharmony_ci	}
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci	/*
1438c2ecf20Sopenharmony_ci	 * We should check if the COEFFICIENTS register is supported.
1448c2ecf20Sopenharmony_ci	 * If it is, and the chip is configured for direct mode, we can read
1458c2ecf20Sopenharmony_ci	 * the coefficients from the chip, one set per group of sensor
1468c2ecf20Sopenharmony_ci	 * registers.
1478c2ecf20Sopenharmony_ci	 *
1488c2ecf20Sopenharmony_ci	 * To do this, we will need access to a chip which actually supports the
1498c2ecf20Sopenharmony_ci	 * COEFFICIENTS command, since the command is too complex to implement
1508c2ecf20Sopenharmony_ci	 * without testing it. Until then, abort if a chip configured for direct
1518c2ecf20Sopenharmony_ci	 * mode was detected.
1528c2ecf20Sopenharmony_ci	 */
1538c2ecf20Sopenharmony_ci	if (info->format[PSC_VOLTAGE_OUT] == direct) {
1548c2ecf20Sopenharmony_ci		ret = -ENODEV;
1558c2ecf20Sopenharmony_ci		goto abort;
1568c2ecf20Sopenharmony_ci	}
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ci	/* Try to find sensor groups  */
1598c2ecf20Sopenharmony_ci	pmbus_find_sensor_groups(client, info);
1608c2ecf20Sopenharmony_ciabort:
1618c2ecf20Sopenharmony_ci	return ret;
1628c2ecf20Sopenharmony_ci}
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_cistatic int pmbus_probe(struct i2c_client *client)
1658c2ecf20Sopenharmony_ci{
1668c2ecf20Sopenharmony_ci	struct pmbus_driver_info *info;
1678c2ecf20Sopenharmony_ci	struct pmbus_platform_data *pdata = NULL;
1688c2ecf20Sopenharmony_ci	struct device *dev = &client->dev;
1698c2ecf20Sopenharmony_ci	struct pmbus_device_info *device_info;
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_ci	info = devm_kzalloc(dev, sizeof(struct pmbus_driver_info), GFP_KERNEL);
1728c2ecf20Sopenharmony_ci	if (!info)
1738c2ecf20Sopenharmony_ci		return -ENOMEM;
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	device_info = (struct pmbus_device_info *)i2c_match_id(pmbus_id, client)->driver_data;
1768c2ecf20Sopenharmony_ci	if (device_info->flags & PMBUS_SKIP_STATUS_CHECK) {
1778c2ecf20Sopenharmony_ci		pdata = devm_kzalloc(dev, sizeof(struct pmbus_platform_data),
1788c2ecf20Sopenharmony_ci				     GFP_KERNEL);
1798c2ecf20Sopenharmony_ci		if (!pdata)
1808c2ecf20Sopenharmony_ci			return -ENOMEM;
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci		pdata->flags = PMBUS_SKIP_STATUS_CHECK;
1838c2ecf20Sopenharmony_ci	}
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_ci	info->pages = device_info->pages;
1868c2ecf20Sopenharmony_ci	info->identify = pmbus_identify;
1878c2ecf20Sopenharmony_ci	dev->platform_data = pdata;
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci	return pmbus_do_probe(client, info);
1908c2ecf20Sopenharmony_ci}
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_cistatic const struct pmbus_device_info pmbus_info_one = {
1938c2ecf20Sopenharmony_ci	.pages = 1,
1948c2ecf20Sopenharmony_ci	.flags = 0
1958c2ecf20Sopenharmony_ci};
1968c2ecf20Sopenharmony_cistatic const struct pmbus_device_info pmbus_info_zero = {
1978c2ecf20Sopenharmony_ci	.pages = 0,
1988c2ecf20Sopenharmony_ci	.flags = 0
1998c2ecf20Sopenharmony_ci};
2008c2ecf20Sopenharmony_cistatic const struct pmbus_device_info pmbus_info_one_skip = {
2018c2ecf20Sopenharmony_ci	.pages = 1,
2028c2ecf20Sopenharmony_ci	.flags = PMBUS_SKIP_STATUS_CHECK
2038c2ecf20Sopenharmony_ci};
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ci/*
2068c2ecf20Sopenharmony_ci * Use driver_data to set the number of pages supported by the chip.
2078c2ecf20Sopenharmony_ci */
2088c2ecf20Sopenharmony_cistatic const struct i2c_device_id pmbus_id[] = {
2098c2ecf20Sopenharmony_ci	{"adp4000", (kernel_ulong_t)&pmbus_info_one},
2108c2ecf20Sopenharmony_ci	{"bmr453", (kernel_ulong_t)&pmbus_info_one},
2118c2ecf20Sopenharmony_ci	{"bmr454", (kernel_ulong_t)&pmbus_info_one},
2128c2ecf20Sopenharmony_ci	{"dps460", (kernel_ulong_t)&pmbus_info_one_skip},
2138c2ecf20Sopenharmony_ci	{"dps650ab", (kernel_ulong_t)&pmbus_info_one_skip},
2148c2ecf20Sopenharmony_ci	{"dps800", (kernel_ulong_t)&pmbus_info_one_skip},
2158c2ecf20Sopenharmony_ci	{"max20796", (kernel_ulong_t)&pmbus_info_one},
2168c2ecf20Sopenharmony_ci	{"mdt040", (kernel_ulong_t)&pmbus_info_one},
2178c2ecf20Sopenharmony_ci	{"ncp4200", (kernel_ulong_t)&pmbus_info_one},
2188c2ecf20Sopenharmony_ci	{"ncp4208", (kernel_ulong_t)&pmbus_info_one},
2198c2ecf20Sopenharmony_ci	{"pdt003", (kernel_ulong_t)&pmbus_info_one},
2208c2ecf20Sopenharmony_ci	{"pdt006", (kernel_ulong_t)&pmbus_info_one},
2218c2ecf20Sopenharmony_ci	{"pdt012", (kernel_ulong_t)&pmbus_info_one},
2228c2ecf20Sopenharmony_ci	{"pmbus", (kernel_ulong_t)&pmbus_info_zero},
2238c2ecf20Sopenharmony_ci	{"sgd009", (kernel_ulong_t)&pmbus_info_one_skip},
2248c2ecf20Sopenharmony_ci	{"tps40400", (kernel_ulong_t)&pmbus_info_one},
2258c2ecf20Sopenharmony_ci	{"tps544b20", (kernel_ulong_t)&pmbus_info_one},
2268c2ecf20Sopenharmony_ci	{"tps544b25", (kernel_ulong_t)&pmbus_info_one},
2278c2ecf20Sopenharmony_ci	{"tps544c20", (kernel_ulong_t)&pmbus_info_one},
2288c2ecf20Sopenharmony_ci	{"tps544c25", (kernel_ulong_t)&pmbus_info_one},
2298c2ecf20Sopenharmony_ci	{"udt020", (kernel_ulong_t)&pmbus_info_one},
2308c2ecf20Sopenharmony_ci	{}
2318c2ecf20Sopenharmony_ci};
2328c2ecf20Sopenharmony_ci
2338c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, pmbus_id);
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_ci/* This is the driver that will be inserted */
2368c2ecf20Sopenharmony_cistatic struct i2c_driver pmbus_driver = {
2378c2ecf20Sopenharmony_ci	.driver = {
2388c2ecf20Sopenharmony_ci		   .name = "pmbus",
2398c2ecf20Sopenharmony_ci		   },
2408c2ecf20Sopenharmony_ci	.probe_new = pmbus_probe,
2418c2ecf20Sopenharmony_ci	.remove = pmbus_do_remove,
2428c2ecf20Sopenharmony_ci	.id_table = pmbus_id,
2438c2ecf20Sopenharmony_ci};
2448c2ecf20Sopenharmony_ci
2458c2ecf20Sopenharmony_cimodule_i2c_driver(pmbus_driver);
2468c2ecf20Sopenharmony_ci
2478c2ecf20Sopenharmony_ciMODULE_AUTHOR("Guenter Roeck");
2488c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Generic PMBus driver");
2498c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
250