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