xref: /linux/drivers/hwmon/pmbus/fsp-3y.c (revision cdd5b5a9761fd66d17586e4f4ba6588c70e640ea)
11734b413SVáclav Kubernát // SPDX-License-Identifier: GPL-2.0-or-later
21734b413SVáclav Kubernát /*
31734b413SVáclav Kubernát  * Hardware monitoring driver for FSP 3Y-Power PSUs
41734b413SVáclav Kubernát  *
51734b413SVáclav Kubernát  * Copyright (c) 2021 Václav Kubernát, CESNET
61734b413SVáclav Kubernát  *
71734b413SVáclav Kubernát  * This driver is mostly reverse engineered with the help of a tool called pmbus_peek written by
81734b413SVáclav Kubernát  * David Brownell (and later adopted by Jan Kundrát). The device has some sort of a timing issue
91734b413SVáclav Kubernát  * when switching pages, details are explained in the code. The driver support is limited. It
101734b413SVáclav Kubernát  * exposes only the values, that have been tested to work correctly. Unsupported values either
111734b413SVáclav Kubernát  * aren't supported by the devices or their encondings are unknown.
121734b413SVáclav Kubernát  */
131734b413SVáclav Kubernát 
141734b413SVáclav Kubernát #include <linux/delay.h>
151734b413SVáclav Kubernát #include <linux/i2c.h>
161734b413SVáclav Kubernát #include <linux/kernel.h>
171734b413SVáclav Kubernát #include <linux/module.h>
181734b413SVáclav Kubernát #include "pmbus.h"
191734b413SVáclav Kubernát 
201734b413SVáclav Kubernát #define YM2151_PAGE_12V_LOG	0x00
211734b413SVáclav Kubernát #define YM2151_PAGE_12V_REAL	0x00
221734b413SVáclav Kubernát #define YM2151_PAGE_5VSB_LOG	0x01
231734b413SVáclav Kubernát #define YM2151_PAGE_5VSB_REAL	0x20
241734b413SVáclav Kubernát #define YH5151E_PAGE_12V_LOG	0x00
251734b413SVáclav Kubernát #define YH5151E_PAGE_12V_REAL	0x00
261734b413SVáclav Kubernát #define YH5151E_PAGE_5V_LOG	0x01
271734b413SVáclav Kubernát #define YH5151E_PAGE_5V_REAL	0x10
281734b413SVáclav Kubernát #define YH5151E_PAGE_3V3_LOG	0x02
291734b413SVáclav Kubernát #define YH5151E_PAGE_3V3_REAL	0x11
301734b413SVáclav Kubernát 
311734b413SVáclav Kubernát enum chips {
321734b413SVáclav Kubernát 	ym2151e,
331734b413SVáclav Kubernát 	yh5151e
341734b413SVáclav Kubernát };
351734b413SVáclav Kubernát 
361734b413SVáclav Kubernát struct fsp3y_data {
371734b413SVáclav Kubernát 	struct pmbus_driver_info info;
381734b413SVáclav Kubernát 	int chip;
391734b413SVáclav Kubernát 	int page;
40c2a338c9SVáclav Kubernát 
41c2a338c9SVáclav Kubernát 	bool vout_linear_11;
421734b413SVáclav Kubernát };
431734b413SVáclav Kubernát 
441734b413SVáclav Kubernát #define to_fsp3y_data(x) container_of(x, struct fsp3y_data, info)
451734b413SVáclav Kubernát 
page_log_to_page_real(int page_log,enum chips chip)461734b413SVáclav Kubernát static int page_log_to_page_real(int page_log, enum chips chip)
471734b413SVáclav Kubernát {
481734b413SVáclav Kubernát 	switch (chip) {
491734b413SVáclav Kubernát 	case ym2151e:
501734b413SVáclav Kubernát 		switch (page_log) {
511734b413SVáclav Kubernát 		case YM2151_PAGE_12V_LOG:
521734b413SVáclav Kubernát 			return YM2151_PAGE_12V_REAL;
531734b413SVáclav Kubernát 		case YM2151_PAGE_5VSB_LOG:
541734b413SVáclav Kubernát 			return YM2151_PAGE_5VSB_REAL;
551734b413SVáclav Kubernát 		}
561734b413SVáclav Kubernát 		return -EINVAL;
571734b413SVáclav Kubernát 	case yh5151e:
581734b413SVáclav Kubernát 		switch (page_log) {
591734b413SVáclav Kubernát 		case YH5151E_PAGE_12V_LOG:
601734b413SVáclav Kubernát 			return YH5151E_PAGE_12V_REAL;
611734b413SVáclav Kubernát 		case YH5151E_PAGE_5V_LOG:
622d101db3SVáclav Kubernát 			return YH5151E_PAGE_5V_REAL;
631734b413SVáclav Kubernát 		case YH5151E_PAGE_3V3_LOG:
641734b413SVáclav Kubernát 			return YH5151E_PAGE_3V3_REAL;
651734b413SVáclav Kubernát 		}
661734b413SVáclav Kubernát 		return -EINVAL;
671734b413SVáclav Kubernát 	}
681734b413SVáclav Kubernát 
691734b413SVáclav Kubernát 	return -EINVAL;
701734b413SVáclav Kubernát }
711734b413SVáclav Kubernát 
set_page(struct i2c_client * client,int page_log)721734b413SVáclav Kubernát static int set_page(struct i2c_client *client, int page_log)
731734b413SVáclav Kubernát {
741734b413SVáclav Kubernát 	const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
751734b413SVáclav Kubernát 	struct fsp3y_data *data = to_fsp3y_data(info);
761734b413SVáclav Kubernát 	int rv;
771734b413SVáclav Kubernát 	int page_real;
781734b413SVáclav Kubernát 
791734b413SVáclav Kubernát 	if (page_log < 0)
801734b413SVáclav Kubernát 		return 0;
811734b413SVáclav Kubernát 
821734b413SVáclav Kubernát 	page_real = page_log_to_page_real(page_log, data->chip);
831734b413SVáclav Kubernát 	if (page_real < 0)
841734b413SVáclav Kubernát 		return page_real;
851734b413SVáclav Kubernát 
861734b413SVáclav Kubernát 	if (data->page != page_real) {
871734b413SVáclav Kubernát 		rv = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page_real);
881734b413SVáclav Kubernát 		if (rv < 0)
891734b413SVáclav Kubernát 			return rv;
901734b413SVáclav Kubernát 
911734b413SVáclav Kubernát 		data->page = page_real;
921734b413SVáclav Kubernát 
931734b413SVáclav Kubernát 		/*
941734b413SVáclav Kubernát 		 * Testing showed that the device has a timing issue. After
951734b413SVáclav Kubernát 		 * setting a page, it takes a while, before the device actually
961734b413SVáclav Kubernát 		 * gives the correct values from the correct page. 20 ms was
971734b413SVáclav Kubernát 		 * tested to be enough to not give wrong values (15 ms wasn't
981734b413SVáclav Kubernát 		 * enough).
991734b413SVáclav Kubernát 		 */
1001734b413SVáclav Kubernát 		usleep_range(20000, 30000);
1011734b413SVáclav Kubernát 	}
1021734b413SVáclav Kubernát 
1031734b413SVáclav Kubernát 	return 0;
1041734b413SVáclav Kubernát }
1051734b413SVáclav Kubernát 
fsp3y_read_byte_data(struct i2c_client * client,int page,int reg)1061734b413SVáclav Kubernát static int fsp3y_read_byte_data(struct i2c_client *client, int page, int reg)
1071734b413SVáclav Kubernát {
1082d101db3SVáclav Kubernát 	const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
1092d101db3SVáclav Kubernát 	struct fsp3y_data *data = to_fsp3y_data(info);
1101734b413SVáclav Kubernát 	int rv;
1111734b413SVáclav Kubernát 
1122d101db3SVáclav Kubernát 	/*
113c2a338c9SVáclav Kubernát 	 * Inject an exponent for non-compliant YH5151-E.
1142d101db3SVáclav Kubernát 	 */
115c2a338c9SVáclav Kubernát 	if (data->vout_linear_11 && reg == PMBUS_VOUT_MODE)
1162d101db3SVáclav Kubernát 		return 0x1A;
1172d101db3SVáclav Kubernát 
1181734b413SVáclav Kubernát 	rv = set_page(client, page);
1191734b413SVáclav Kubernát 	if (rv < 0)
1201734b413SVáclav Kubernát 		return rv;
1211734b413SVáclav Kubernát 
1221734b413SVáclav Kubernát 	return i2c_smbus_read_byte_data(client, reg);
1231734b413SVáclav Kubernát }
1241734b413SVáclav Kubernát 
fsp3y_read_word_data(struct i2c_client * client,int page,int phase,int reg)1251734b413SVáclav Kubernát static int fsp3y_read_word_data(struct i2c_client *client, int page, int phase, int reg)
1261734b413SVáclav Kubernát {
1272d101db3SVáclav Kubernát 	const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
1282d101db3SVáclav Kubernát 	struct fsp3y_data *data = to_fsp3y_data(info);
1291734b413SVáclav Kubernát 	int rv;
1301734b413SVáclav Kubernát 
1311734b413SVáclav Kubernát 	/*
1321734b413SVáclav Kubernát 	 * This masks commands which weren't tested to work correctly. Some of
1331734b413SVáclav Kubernát 	 * the masked commands return 0xFFFF. These would probably get tagged as
1341734b413SVáclav Kubernát 	 * invalid by pmbus_core. Other ones do return values which might be
1351734b413SVáclav Kubernát 	 * useful (that is, they are not 0xFFFF), but their encoding is unknown,
1361734b413SVáclav Kubernát 	 * and so they are unsupported.
1371734b413SVáclav Kubernát 	 */
1381734b413SVáclav Kubernát 	switch (reg) {
1391734b413SVáclav Kubernát 	case PMBUS_READ_FAN_SPEED_1:
1401734b413SVáclav Kubernát 	case PMBUS_READ_IIN:
1411734b413SVáclav Kubernát 	case PMBUS_READ_IOUT:
1421734b413SVáclav Kubernát 	case PMBUS_READ_PIN:
1431734b413SVáclav Kubernát 	case PMBUS_READ_POUT:
1441734b413SVáclav Kubernát 	case PMBUS_READ_TEMPERATURE_1:
1451734b413SVáclav Kubernát 	case PMBUS_READ_TEMPERATURE_2:
1461734b413SVáclav Kubernát 	case PMBUS_READ_TEMPERATURE_3:
1471734b413SVáclav Kubernát 	case PMBUS_READ_VIN:
1481734b413SVáclav Kubernát 	case PMBUS_READ_VOUT:
1491734b413SVáclav Kubernát 	case PMBUS_STATUS_WORD:
1501734b413SVáclav Kubernát 		break;
1511734b413SVáclav Kubernát 	default:
1521734b413SVáclav Kubernát 		return -ENXIO;
1531734b413SVáclav Kubernát 	}
1541734b413SVáclav Kubernát 
1551734b413SVáclav Kubernát 	rv = set_page(client, page);
1561734b413SVáclav Kubernát 	if (rv < 0)
1571734b413SVáclav Kubernát 		return rv;
1581734b413SVáclav Kubernát 
1592d101db3SVáclav Kubernát 	rv = i2c_smbus_read_word_data(client, reg);
1602d101db3SVáclav Kubernát 	if (rv < 0)
1612d101db3SVáclav Kubernát 		return rv;
1622d101db3SVáclav Kubernát 
1632d101db3SVáclav Kubernát 	/*
164c2a338c9SVáclav Kubernát 	 * Handle YH-5151E non-compliant linear11 vout voltage.
1652d101db3SVáclav Kubernát 	 */
166c2a338c9SVáclav Kubernát 	if (data->vout_linear_11 && reg == PMBUS_READ_VOUT)
1672d101db3SVáclav Kubernát 		rv = sign_extend32(rv, 10) & 0xffff;
1682d101db3SVáclav Kubernát 
1692d101db3SVáclav Kubernát 	return rv;
1701734b413SVáclav Kubernát }
1711734b413SVáclav Kubernát 
1721734b413SVáclav Kubernát static struct pmbus_driver_info fsp3y_info[] = {
1731734b413SVáclav Kubernát 	[ym2151e] = {
1741734b413SVáclav Kubernát 		.pages = 2,
1751734b413SVáclav Kubernát 		.func[YM2151_PAGE_12V_LOG] =
1761734b413SVáclav Kubernát 			PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT |
1771734b413SVáclav Kubernát 			PMBUS_HAVE_PIN | PMBUS_HAVE_POUT  |
1781734b413SVáclav Kubernát 			PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 |
1791734b413SVáclav Kubernát 			PMBUS_HAVE_VIN | PMBUS_HAVE_IIN |
1801734b413SVáclav Kubernát 			PMBUS_HAVE_FAN12,
1811734b413SVáclav Kubernát 		.func[YM2151_PAGE_5VSB_LOG] =
1821734b413SVáclav Kubernát 			PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT,
1831734b413SVáclav Kubernát 		.read_word_data = fsp3y_read_word_data,
1841734b413SVáclav Kubernát 		.read_byte_data = fsp3y_read_byte_data,
1851734b413SVáclav Kubernát 	},
1861734b413SVáclav Kubernát 	[yh5151e] = {
1871734b413SVáclav Kubernát 		.pages = 3,
1881734b413SVáclav Kubernát 		.func[YH5151E_PAGE_12V_LOG] =
1891734b413SVáclav Kubernát 			PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT |
1901734b413SVáclav Kubernát 			PMBUS_HAVE_POUT  |
1911734b413SVáclav Kubernát 			PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_TEMP3,
1921734b413SVáclav Kubernát 		.func[YH5151E_PAGE_5V_LOG] =
1931734b413SVáclav Kubernát 			PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT |
1941734b413SVáclav Kubernát 			PMBUS_HAVE_POUT,
1951734b413SVáclav Kubernát 		.func[YH5151E_PAGE_3V3_LOG] =
1961734b413SVáclav Kubernát 			PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT |
1971734b413SVáclav Kubernát 			PMBUS_HAVE_POUT,
1981734b413SVáclav Kubernát 		.read_word_data = fsp3y_read_word_data,
1991734b413SVáclav Kubernát 		.read_byte_data = fsp3y_read_byte_data,
2001734b413SVáclav Kubernát 	}
2011734b413SVáclav Kubernát };
2021734b413SVáclav Kubernát 
fsp3y_detect(struct i2c_client * client)2031734b413SVáclav Kubernát static int fsp3y_detect(struct i2c_client *client)
2041734b413SVáclav Kubernát {
2051734b413SVáclav Kubernát 	int rv;
2061734b413SVáclav Kubernát 	u8 buf[I2C_SMBUS_BLOCK_MAX + 1];
2071734b413SVáclav Kubernát 
2081734b413SVáclav Kubernát 	rv = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf);
2091734b413SVáclav Kubernát 	if (rv < 0)
2101734b413SVáclav Kubernát 		return rv;
2111734b413SVáclav Kubernát 
2121734b413SVáclav Kubernát 	buf[rv] = '\0';
2131734b413SVáclav Kubernát 
2141734b413SVáclav Kubernát 	if (rv == 8) {
2151734b413SVáclav Kubernát 		if (!strcmp(buf, "YM-2151E"))
2161734b413SVáclav Kubernát 			return ym2151e;
2171734b413SVáclav Kubernát 		else if (!strcmp(buf, "YH-5151E"))
2181734b413SVáclav Kubernát 			return yh5151e;
2191734b413SVáclav Kubernát 	}
2201734b413SVáclav Kubernát 
2211734b413SVáclav Kubernát 	dev_err(&client->dev, "Unsupported model %.*s\n", rv, buf);
2221734b413SVáclav Kubernát 	return -ENODEV;
2231734b413SVáclav Kubernát }
2241734b413SVáclav Kubernát 
2251734b413SVáclav Kubernát static const struct i2c_device_id fsp3y_id[] = {
2261734b413SVáclav Kubernát 	{"ym2151e", ym2151e},
2271734b413SVáclav Kubernát 	{"yh5151e", yh5151e},
2281734b413SVáclav Kubernát 	{ }
2291734b413SVáclav Kubernát };
2301734b413SVáclav Kubernát 
fsp3y_probe(struct i2c_client * client)2311734b413SVáclav Kubernát static int fsp3y_probe(struct i2c_client *client)
2321734b413SVáclav Kubernát {
2331734b413SVáclav Kubernát 	struct fsp3y_data *data;
2341734b413SVáclav Kubernát 	const struct i2c_device_id *id;
2351734b413SVáclav Kubernát 	int rv;
2361734b413SVáclav Kubernát 
2371734b413SVáclav Kubernát 	data = devm_kzalloc(&client->dev, sizeof(struct fsp3y_data), GFP_KERNEL);
2381734b413SVáclav Kubernát 	if (!data)
2391734b413SVáclav Kubernát 		return -ENOMEM;
2401734b413SVáclav Kubernát 
2411734b413SVáclav Kubernát 	data->chip = fsp3y_detect(client);
2421734b413SVáclav Kubernát 	if (data->chip < 0)
2431734b413SVáclav Kubernát 		return data->chip;
2441734b413SVáclav Kubernát 
2451734b413SVáclav Kubernát 	id = i2c_match_id(fsp3y_id, client);
2461734b413SVáclav Kubernát 	if (data->chip != id->driver_data)
2471734b413SVáclav Kubernát 		dev_warn(&client->dev, "Device mismatch: Configured %s (%d), detected %d\n",
2481734b413SVáclav Kubernát 			 id->name, (int)id->driver_data, data->chip);
2491734b413SVáclav Kubernát 
2501734b413SVáclav Kubernát 	rv = i2c_smbus_read_byte_data(client, PMBUS_PAGE);
2511734b413SVáclav Kubernát 	if (rv < 0)
2521734b413SVáclav Kubernát 		return rv;
2531734b413SVáclav Kubernát 	data->page = rv;
2541734b413SVáclav Kubernát 
2551734b413SVáclav Kubernát 	data->info = fsp3y_info[data->chip];
2561734b413SVáclav Kubernát 
257c2a338c9SVáclav Kubernát 	/*
258c2a338c9SVáclav Kubernát 	 * YH-5151E sometimes reports vout in linear11 and sometimes in
259c2a338c9SVáclav Kubernát 	 * linear16. This depends on the exact individual piece of hardware. One
260c2a338c9SVáclav Kubernát 	 * YH-5151E can use linear16 and another might use linear11 instead.
261c2a338c9SVáclav Kubernát 	 *
262c2a338c9SVáclav Kubernát 	 * The format can be recognized by reading VOUT_MODE - if it doesn't
263c2a338c9SVáclav Kubernát 	 * report a valid exponent, then vout uses linear11. Otherwise, the
264c2a338c9SVáclav Kubernát 	 * device is compliant and uses linear16.
265c2a338c9SVáclav Kubernát 	 */
266c2a338c9SVáclav Kubernát 	data->vout_linear_11 = false;
267c2a338c9SVáclav Kubernát 	if (data->chip == yh5151e) {
268c2a338c9SVáclav Kubernát 		rv = i2c_smbus_read_byte_data(client, PMBUS_VOUT_MODE);
269c2a338c9SVáclav Kubernát 		if (rv < 0)
270c2a338c9SVáclav Kubernát 			return rv;
271c2a338c9SVáclav Kubernát 
272c2a338c9SVáclav Kubernát 		if (rv == 0xFF)
273c2a338c9SVáclav Kubernát 			data->vout_linear_11 = true;
274c2a338c9SVáclav Kubernát 	}
275c2a338c9SVáclav Kubernát 
2761734b413SVáclav Kubernát 	return pmbus_do_probe(client, &data->info);
2771734b413SVáclav Kubernát }
2781734b413SVáclav Kubernát 
2791734b413SVáclav Kubernát MODULE_DEVICE_TABLE(i2c, fsp3y_id);
2801734b413SVáclav Kubernát 
2811734b413SVáclav Kubernát static struct i2c_driver fsp3y_driver = {
2821734b413SVáclav Kubernát 	.driver = {
2831734b413SVáclav Kubernát 		   .name = "fsp3y",
2841734b413SVáclav Kubernát 		   },
285*1975d167SUwe Kleine-König 	.probe = fsp3y_probe,
2861734b413SVáclav Kubernát 	.id_table = fsp3y_id
2871734b413SVáclav Kubernát };
2881734b413SVáclav Kubernát 
2891734b413SVáclav Kubernát module_i2c_driver(fsp3y_driver);
2901734b413SVáclav Kubernát 
2911734b413SVáclav Kubernát MODULE_AUTHOR("Václav Kubernát");
2921734b413SVáclav Kubernát MODULE_DESCRIPTION("PMBus driver for FSP/3Y-Power power supplies");
2931734b413SVáclav Kubernát MODULE_LICENSE("GPL");
294b94ca77eSGuenter Roeck MODULE_IMPORT_NS(PMBUS);
295