xref: /linux/drivers/hwmon/pmbus/fsp-3y.c (revision 2d101db3e5be3bbee6001d4227705cec70ecb82e)
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;
401734b413SVáclav Kubernát };
411734b413SVáclav Kubernát 
421734b413SVáclav Kubernát #define to_fsp3y_data(x) container_of(x, struct fsp3y_data, info)
431734b413SVáclav Kubernát 
441734b413SVáclav Kubernát static int page_log_to_page_real(int page_log, enum chips chip)
451734b413SVáclav Kubernát {
461734b413SVáclav Kubernát 	switch (chip) {
471734b413SVáclav Kubernát 	case ym2151e:
481734b413SVáclav Kubernát 		switch (page_log) {
491734b413SVáclav Kubernát 		case YM2151_PAGE_12V_LOG:
501734b413SVáclav Kubernát 			return YM2151_PAGE_12V_REAL;
511734b413SVáclav Kubernát 		case YM2151_PAGE_5VSB_LOG:
521734b413SVáclav Kubernát 			return YM2151_PAGE_5VSB_REAL;
531734b413SVáclav Kubernát 		}
541734b413SVáclav Kubernát 		return -EINVAL;
551734b413SVáclav Kubernát 	case yh5151e:
561734b413SVáclav Kubernát 		switch (page_log) {
571734b413SVáclav Kubernát 		case YH5151E_PAGE_12V_LOG:
581734b413SVáclav Kubernát 			return YH5151E_PAGE_12V_REAL;
591734b413SVáclav Kubernát 		case YH5151E_PAGE_5V_LOG:
60*2d101db3SVáclav Kubernát 			return YH5151E_PAGE_5V_REAL;
611734b413SVáclav Kubernát 		case YH5151E_PAGE_3V3_LOG:
621734b413SVáclav Kubernát 			return YH5151E_PAGE_3V3_REAL;
631734b413SVáclav Kubernát 		}
641734b413SVáclav Kubernát 		return -EINVAL;
651734b413SVáclav Kubernát 	}
661734b413SVáclav Kubernát 
671734b413SVáclav Kubernát 	return -EINVAL;
681734b413SVáclav Kubernát }
691734b413SVáclav Kubernát 
701734b413SVáclav Kubernát static int set_page(struct i2c_client *client, int page_log)
711734b413SVáclav Kubernát {
721734b413SVáclav Kubernát 	const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
731734b413SVáclav Kubernát 	struct fsp3y_data *data = to_fsp3y_data(info);
741734b413SVáclav Kubernát 	int rv;
751734b413SVáclav Kubernát 	int page_real;
761734b413SVáclav Kubernát 
771734b413SVáclav Kubernát 	if (page_log < 0)
781734b413SVáclav Kubernát 		return 0;
791734b413SVáclav Kubernát 
801734b413SVáclav Kubernát 	page_real = page_log_to_page_real(page_log, data->chip);
811734b413SVáclav Kubernát 	if (page_real < 0)
821734b413SVáclav Kubernát 		return page_real;
831734b413SVáclav Kubernát 
841734b413SVáclav Kubernát 	if (data->page != page_real) {
851734b413SVáclav Kubernát 		rv = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page_real);
861734b413SVáclav Kubernát 		if (rv < 0)
871734b413SVáclav Kubernát 			return rv;
881734b413SVáclav Kubernát 
891734b413SVáclav Kubernát 		data->page = page_real;
901734b413SVáclav Kubernát 
911734b413SVáclav Kubernát 		/*
921734b413SVáclav Kubernát 		 * Testing showed that the device has a timing issue. After
931734b413SVáclav Kubernát 		 * setting a page, it takes a while, before the device actually
941734b413SVáclav Kubernát 		 * gives the correct values from the correct page. 20 ms was
951734b413SVáclav Kubernát 		 * tested to be enough to not give wrong values (15 ms wasn't
961734b413SVáclav Kubernát 		 * enough).
971734b413SVáclav Kubernát 		 */
981734b413SVáclav Kubernát 		usleep_range(20000, 30000);
991734b413SVáclav Kubernát 	}
1001734b413SVáclav Kubernát 
1011734b413SVáclav Kubernát 	return 0;
1021734b413SVáclav Kubernát }
1031734b413SVáclav Kubernát 
1041734b413SVáclav Kubernát static int fsp3y_read_byte_data(struct i2c_client *client, int page, int reg)
1051734b413SVáclav Kubernát {
106*2d101db3SVáclav Kubernát 	const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
107*2d101db3SVáclav Kubernát 	struct fsp3y_data *data = to_fsp3y_data(info);
1081734b413SVáclav Kubernát 	int rv;
1091734b413SVáclav Kubernát 
110*2d101db3SVáclav Kubernát 	/*
111*2d101db3SVáclav Kubernát 	 * YH5151-E outputs vout in linear11. The conversion is done when
112*2d101db3SVáclav Kubernát 	 * reading. Here, we have to inject pmbus_core with the correct
113*2d101db3SVáclav Kubernát 	 * exponent (it is -6).
114*2d101db3SVáclav Kubernát 	 */
115*2d101db3SVáclav Kubernát 	if (data->chip == yh5151e && reg == PMBUS_VOUT_MODE)
116*2d101db3SVáclav Kubernát 		return 0x1A;
117*2d101db3SVá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 
1251734b413SVáclav Kubernát static int fsp3y_read_word_data(struct i2c_client *client, int page, int phase, int reg)
1261734b413SVáclav Kubernát {
127*2d101db3SVáclav Kubernát 	const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
128*2d101db3SVá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 
159*2d101db3SVáclav Kubernát 	rv = i2c_smbus_read_word_data(client, reg);
160*2d101db3SVáclav Kubernát 	if (rv < 0)
161*2d101db3SVáclav Kubernát 		return rv;
162*2d101db3SVáclav Kubernát 
163*2d101db3SVáclav Kubernát 	/*
164*2d101db3SVáclav Kubernát 	 * YH-5151E is non-compliant and outputs output voltages in linear11
165*2d101db3SVáclav Kubernát 	 * instead of linear16.
166*2d101db3SVáclav Kubernát 	 */
167*2d101db3SVáclav Kubernát 	if (data->chip == yh5151e && reg == PMBUS_READ_VOUT)
168*2d101db3SVáclav Kubernát 		rv = sign_extend32(rv, 10) & 0xffff;
169*2d101db3SVáclav Kubernát 
170*2d101db3SVáclav Kubernát 	return rv;
1711734b413SVáclav Kubernát }
1721734b413SVáclav Kubernát 
1731734b413SVáclav Kubernát static struct pmbus_driver_info fsp3y_info[] = {
1741734b413SVáclav Kubernát 	[ym2151e] = {
1751734b413SVáclav Kubernát 		.pages = 2,
1761734b413SVáclav Kubernát 		.func[YM2151_PAGE_12V_LOG] =
1771734b413SVáclav Kubernát 			PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT |
1781734b413SVáclav Kubernát 			PMBUS_HAVE_PIN | PMBUS_HAVE_POUT  |
1791734b413SVáclav Kubernát 			PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 |
1801734b413SVáclav Kubernát 			PMBUS_HAVE_VIN | PMBUS_HAVE_IIN |
1811734b413SVáclav Kubernát 			PMBUS_HAVE_FAN12,
1821734b413SVáclav Kubernát 		.func[YM2151_PAGE_5VSB_LOG] =
1831734b413SVáclav Kubernát 			PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT,
1841734b413SVáclav Kubernát 			PMBUS_HAVE_IIN,
1851734b413SVáclav Kubernát 		.read_word_data = fsp3y_read_word_data,
1861734b413SVáclav Kubernát 		.read_byte_data = fsp3y_read_byte_data,
1871734b413SVáclav Kubernát 	},
1881734b413SVáclav Kubernát 	[yh5151e] = {
1891734b413SVáclav Kubernát 		.pages = 3,
1901734b413SVáclav Kubernát 		.func[YH5151E_PAGE_12V_LOG] =
1911734b413SVáclav Kubernát 			PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT |
1921734b413SVáclav Kubernát 			PMBUS_HAVE_POUT  |
1931734b413SVáclav Kubernát 			PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_TEMP3,
1941734b413SVáclav Kubernát 		.func[YH5151E_PAGE_5V_LOG] =
1951734b413SVáclav Kubernát 			PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT |
1961734b413SVáclav Kubernát 			PMBUS_HAVE_POUT,
1971734b413SVáclav Kubernát 		.func[YH5151E_PAGE_3V3_LOG] =
1981734b413SVáclav Kubernát 			PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT |
1991734b413SVáclav Kubernát 			PMBUS_HAVE_POUT,
2001734b413SVáclav Kubernát 		.read_word_data = fsp3y_read_word_data,
2011734b413SVáclav Kubernát 		.read_byte_data = fsp3y_read_byte_data,
2021734b413SVáclav Kubernát 	}
2031734b413SVáclav Kubernát };
2041734b413SVáclav Kubernát 
2051734b413SVáclav Kubernát static int fsp3y_detect(struct i2c_client *client)
2061734b413SVáclav Kubernát {
2071734b413SVáclav Kubernát 	int rv;
2081734b413SVáclav Kubernát 	u8 buf[I2C_SMBUS_BLOCK_MAX + 1];
2091734b413SVáclav Kubernát 
2101734b413SVáclav Kubernát 	rv = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf);
2111734b413SVáclav Kubernát 	if (rv < 0)
2121734b413SVáclav Kubernát 		return rv;
2131734b413SVáclav Kubernát 
2141734b413SVáclav Kubernát 	buf[rv] = '\0';
2151734b413SVáclav Kubernát 
2161734b413SVáclav Kubernát 	if (rv == 8) {
2171734b413SVáclav Kubernát 		if (!strcmp(buf, "YM-2151E"))
2181734b413SVáclav Kubernát 			return ym2151e;
2191734b413SVáclav Kubernát 		else if (!strcmp(buf, "YH-5151E"))
2201734b413SVáclav Kubernát 			return yh5151e;
2211734b413SVáclav Kubernát 	}
2221734b413SVáclav Kubernát 
2231734b413SVáclav Kubernát 	dev_err(&client->dev, "Unsupported model %.*s\n", rv, buf);
2241734b413SVáclav Kubernát 	return -ENODEV;
2251734b413SVáclav Kubernát }
2261734b413SVáclav Kubernát 
2271734b413SVáclav Kubernát static const struct i2c_device_id fsp3y_id[] = {
2281734b413SVáclav Kubernát 	{"ym2151e", ym2151e},
2291734b413SVáclav Kubernát 	{"yh5151e", yh5151e},
2301734b413SVáclav Kubernát 	{ }
2311734b413SVáclav Kubernát };
2321734b413SVáclav Kubernát 
2331734b413SVáclav Kubernát static int fsp3y_probe(struct i2c_client *client)
2341734b413SVáclav Kubernát {
2351734b413SVáclav Kubernát 	struct fsp3y_data *data;
2361734b413SVáclav Kubernát 	const struct i2c_device_id *id;
2371734b413SVáclav Kubernát 	int rv;
2381734b413SVáclav Kubernát 
2391734b413SVáclav Kubernát 	data = devm_kzalloc(&client->dev, sizeof(struct fsp3y_data), GFP_KERNEL);
2401734b413SVáclav Kubernát 	if (!data)
2411734b413SVáclav Kubernát 		return -ENOMEM;
2421734b413SVáclav Kubernát 
2431734b413SVáclav Kubernát 	data->chip = fsp3y_detect(client);
2441734b413SVáclav Kubernát 	if (data->chip < 0)
2451734b413SVáclav Kubernát 		return data->chip;
2461734b413SVáclav Kubernát 
2471734b413SVáclav Kubernát 	id = i2c_match_id(fsp3y_id, client);
2481734b413SVáclav Kubernát 	if (data->chip != id->driver_data)
2491734b413SVáclav Kubernát 		dev_warn(&client->dev, "Device mismatch: Configured %s (%d), detected %d\n",
2501734b413SVáclav Kubernát 			 id->name, (int)id->driver_data, data->chip);
2511734b413SVáclav Kubernát 
2521734b413SVáclav Kubernát 	rv = i2c_smbus_read_byte_data(client, PMBUS_PAGE);
2531734b413SVáclav Kubernát 	if (rv < 0)
2541734b413SVáclav Kubernát 		return rv;
2551734b413SVáclav Kubernát 	data->page = rv;
2561734b413SVáclav Kubernát 
2571734b413SVáclav Kubernát 	data->info = fsp3y_info[data->chip];
2581734b413SVáclav Kubernát 
2591734b413SVáclav Kubernát 	return pmbus_do_probe(client, &data->info);
2601734b413SVáclav Kubernát }
2611734b413SVáclav Kubernát 
2621734b413SVáclav Kubernát MODULE_DEVICE_TABLE(i2c, fsp3y_id);
2631734b413SVáclav Kubernát 
2641734b413SVáclav Kubernát static struct i2c_driver fsp3y_driver = {
2651734b413SVáclav Kubernát 	.driver = {
2661734b413SVáclav Kubernát 		   .name = "fsp3y",
2671734b413SVáclav Kubernát 		   },
2681734b413SVáclav Kubernát 	.probe_new = fsp3y_probe,
2691734b413SVáclav Kubernát 	.id_table = fsp3y_id
2701734b413SVáclav Kubernát };
2711734b413SVáclav Kubernát 
2721734b413SVáclav Kubernát module_i2c_driver(fsp3y_driver);
2731734b413SVáclav Kubernát 
2741734b413SVáclav Kubernát MODULE_AUTHOR("Václav Kubernát");
2751734b413SVáclav Kubernát MODULE_DESCRIPTION("PMBus driver for FSP/3Y-Power power supplies");
2761734b413SVáclav Kubernát MODULE_LICENSE("GPL");
277b94ca77eSGuenter Roeck MODULE_IMPORT_NS(PMBUS);
278