162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Hardware monitoring driver for FSP 3Y-Power PSUs 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 2021 Václav Kubernát, CESNET 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * This driver is mostly reverse engineered with the help of a tool called pmbus_peek written by 862306a36Sopenharmony_ci * David Brownell (and later adopted by Jan Kundrát). The device has some sort of a timing issue 962306a36Sopenharmony_ci * when switching pages, details are explained in the code. The driver support is limited. It 1062306a36Sopenharmony_ci * exposes only the values, that have been tested to work correctly. Unsupported values either 1162306a36Sopenharmony_ci * aren't supported by the devices or their encondings are unknown. 1262306a36Sopenharmony_ci */ 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci#include <linux/delay.h> 1562306a36Sopenharmony_ci#include <linux/i2c.h> 1662306a36Sopenharmony_ci#include <linux/kernel.h> 1762306a36Sopenharmony_ci#include <linux/module.h> 1862306a36Sopenharmony_ci#include "pmbus.h" 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#define YM2151_PAGE_12V_LOG 0x00 2162306a36Sopenharmony_ci#define YM2151_PAGE_12V_REAL 0x00 2262306a36Sopenharmony_ci#define YM2151_PAGE_5VSB_LOG 0x01 2362306a36Sopenharmony_ci#define YM2151_PAGE_5VSB_REAL 0x20 2462306a36Sopenharmony_ci#define YH5151E_PAGE_12V_LOG 0x00 2562306a36Sopenharmony_ci#define YH5151E_PAGE_12V_REAL 0x00 2662306a36Sopenharmony_ci#define YH5151E_PAGE_5V_LOG 0x01 2762306a36Sopenharmony_ci#define YH5151E_PAGE_5V_REAL 0x10 2862306a36Sopenharmony_ci#define YH5151E_PAGE_3V3_LOG 0x02 2962306a36Sopenharmony_ci#define YH5151E_PAGE_3V3_REAL 0x11 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_cienum chips { 3262306a36Sopenharmony_ci ym2151e, 3362306a36Sopenharmony_ci yh5151e 3462306a36Sopenharmony_ci}; 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_cistruct fsp3y_data { 3762306a36Sopenharmony_ci struct pmbus_driver_info info; 3862306a36Sopenharmony_ci int chip; 3962306a36Sopenharmony_ci int page; 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci bool vout_linear_11; 4262306a36Sopenharmony_ci}; 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci#define to_fsp3y_data(x) container_of(x, struct fsp3y_data, info) 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_cistatic int page_log_to_page_real(int page_log, enum chips chip) 4762306a36Sopenharmony_ci{ 4862306a36Sopenharmony_ci switch (chip) { 4962306a36Sopenharmony_ci case ym2151e: 5062306a36Sopenharmony_ci switch (page_log) { 5162306a36Sopenharmony_ci case YM2151_PAGE_12V_LOG: 5262306a36Sopenharmony_ci return YM2151_PAGE_12V_REAL; 5362306a36Sopenharmony_ci case YM2151_PAGE_5VSB_LOG: 5462306a36Sopenharmony_ci return YM2151_PAGE_5VSB_REAL; 5562306a36Sopenharmony_ci } 5662306a36Sopenharmony_ci return -EINVAL; 5762306a36Sopenharmony_ci case yh5151e: 5862306a36Sopenharmony_ci switch (page_log) { 5962306a36Sopenharmony_ci case YH5151E_PAGE_12V_LOG: 6062306a36Sopenharmony_ci return YH5151E_PAGE_12V_REAL; 6162306a36Sopenharmony_ci case YH5151E_PAGE_5V_LOG: 6262306a36Sopenharmony_ci return YH5151E_PAGE_5V_REAL; 6362306a36Sopenharmony_ci case YH5151E_PAGE_3V3_LOG: 6462306a36Sopenharmony_ci return YH5151E_PAGE_3V3_REAL; 6562306a36Sopenharmony_ci } 6662306a36Sopenharmony_ci return -EINVAL; 6762306a36Sopenharmony_ci } 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci return -EINVAL; 7062306a36Sopenharmony_ci} 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_cistatic int set_page(struct i2c_client *client, int page_log) 7362306a36Sopenharmony_ci{ 7462306a36Sopenharmony_ci const struct pmbus_driver_info *info = pmbus_get_driver_info(client); 7562306a36Sopenharmony_ci struct fsp3y_data *data = to_fsp3y_data(info); 7662306a36Sopenharmony_ci int rv; 7762306a36Sopenharmony_ci int page_real; 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci if (page_log < 0) 8062306a36Sopenharmony_ci return 0; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci page_real = page_log_to_page_real(page_log, data->chip); 8362306a36Sopenharmony_ci if (page_real < 0) 8462306a36Sopenharmony_ci return page_real; 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci if (data->page != page_real) { 8762306a36Sopenharmony_ci rv = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page_real); 8862306a36Sopenharmony_ci if (rv < 0) 8962306a36Sopenharmony_ci return rv; 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci data->page = page_real; 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci /* 9462306a36Sopenharmony_ci * Testing showed that the device has a timing issue. After 9562306a36Sopenharmony_ci * setting a page, it takes a while, before the device actually 9662306a36Sopenharmony_ci * gives the correct values from the correct page. 20 ms was 9762306a36Sopenharmony_ci * tested to be enough to not give wrong values (15 ms wasn't 9862306a36Sopenharmony_ci * enough). 9962306a36Sopenharmony_ci */ 10062306a36Sopenharmony_ci usleep_range(20000, 30000); 10162306a36Sopenharmony_ci } 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci return 0; 10462306a36Sopenharmony_ci} 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_cistatic int fsp3y_read_byte_data(struct i2c_client *client, int page, int reg) 10762306a36Sopenharmony_ci{ 10862306a36Sopenharmony_ci const struct pmbus_driver_info *info = pmbus_get_driver_info(client); 10962306a36Sopenharmony_ci struct fsp3y_data *data = to_fsp3y_data(info); 11062306a36Sopenharmony_ci int rv; 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci /* 11362306a36Sopenharmony_ci * Inject an exponent for non-compliant YH5151-E. 11462306a36Sopenharmony_ci */ 11562306a36Sopenharmony_ci if (data->vout_linear_11 && reg == PMBUS_VOUT_MODE) 11662306a36Sopenharmony_ci return 0x1A; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci rv = set_page(client, page); 11962306a36Sopenharmony_ci if (rv < 0) 12062306a36Sopenharmony_ci return rv; 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci return i2c_smbus_read_byte_data(client, reg); 12362306a36Sopenharmony_ci} 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_cistatic int fsp3y_read_word_data(struct i2c_client *client, int page, int phase, int reg) 12662306a36Sopenharmony_ci{ 12762306a36Sopenharmony_ci const struct pmbus_driver_info *info = pmbus_get_driver_info(client); 12862306a36Sopenharmony_ci struct fsp3y_data *data = to_fsp3y_data(info); 12962306a36Sopenharmony_ci int rv; 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci /* 13262306a36Sopenharmony_ci * This masks commands which weren't tested to work correctly. Some of 13362306a36Sopenharmony_ci * the masked commands return 0xFFFF. These would probably get tagged as 13462306a36Sopenharmony_ci * invalid by pmbus_core. Other ones do return values which might be 13562306a36Sopenharmony_ci * useful (that is, they are not 0xFFFF), but their encoding is unknown, 13662306a36Sopenharmony_ci * and so they are unsupported. 13762306a36Sopenharmony_ci */ 13862306a36Sopenharmony_ci switch (reg) { 13962306a36Sopenharmony_ci case PMBUS_READ_FAN_SPEED_1: 14062306a36Sopenharmony_ci case PMBUS_READ_IIN: 14162306a36Sopenharmony_ci case PMBUS_READ_IOUT: 14262306a36Sopenharmony_ci case PMBUS_READ_PIN: 14362306a36Sopenharmony_ci case PMBUS_READ_POUT: 14462306a36Sopenharmony_ci case PMBUS_READ_TEMPERATURE_1: 14562306a36Sopenharmony_ci case PMBUS_READ_TEMPERATURE_2: 14662306a36Sopenharmony_ci case PMBUS_READ_TEMPERATURE_3: 14762306a36Sopenharmony_ci case PMBUS_READ_VIN: 14862306a36Sopenharmony_ci case PMBUS_READ_VOUT: 14962306a36Sopenharmony_ci case PMBUS_STATUS_WORD: 15062306a36Sopenharmony_ci break; 15162306a36Sopenharmony_ci default: 15262306a36Sopenharmony_ci return -ENXIO; 15362306a36Sopenharmony_ci } 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci rv = set_page(client, page); 15662306a36Sopenharmony_ci if (rv < 0) 15762306a36Sopenharmony_ci return rv; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci rv = i2c_smbus_read_word_data(client, reg); 16062306a36Sopenharmony_ci if (rv < 0) 16162306a36Sopenharmony_ci return rv; 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci /* 16462306a36Sopenharmony_ci * Handle YH-5151E non-compliant linear11 vout voltage. 16562306a36Sopenharmony_ci */ 16662306a36Sopenharmony_ci if (data->vout_linear_11 && reg == PMBUS_READ_VOUT) 16762306a36Sopenharmony_ci rv = sign_extend32(rv, 10) & 0xffff; 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci return rv; 17062306a36Sopenharmony_ci} 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_cistatic struct pmbus_driver_info fsp3y_info[] = { 17362306a36Sopenharmony_ci [ym2151e] = { 17462306a36Sopenharmony_ci .pages = 2, 17562306a36Sopenharmony_ci .func[YM2151_PAGE_12V_LOG] = 17662306a36Sopenharmony_ci PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | 17762306a36Sopenharmony_ci PMBUS_HAVE_PIN | PMBUS_HAVE_POUT | 17862306a36Sopenharmony_ci PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | 17962306a36Sopenharmony_ci PMBUS_HAVE_VIN | PMBUS_HAVE_IIN | 18062306a36Sopenharmony_ci PMBUS_HAVE_FAN12, 18162306a36Sopenharmony_ci .func[YM2151_PAGE_5VSB_LOG] = 18262306a36Sopenharmony_ci PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT, 18362306a36Sopenharmony_ci .read_word_data = fsp3y_read_word_data, 18462306a36Sopenharmony_ci .read_byte_data = fsp3y_read_byte_data, 18562306a36Sopenharmony_ci }, 18662306a36Sopenharmony_ci [yh5151e] = { 18762306a36Sopenharmony_ci .pages = 3, 18862306a36Sopenharmony_ci .func[YH5151E_PAGE_12V_LOG] = 18962306a36Sopenharmony_ci PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | 19062306a36Sopenharmony_ci PMBUS_HAVE_POUT | 19162306a36Sopenharmony_ci PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_TEMP3, 19262306a36Sopenharmony_ci .func[YH5151E_PAGE_5V_LOG] = 19362306a36Sopenharmony_ci PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | 19462306a36Sopenharmony_ci PMBUS_HAVE_POUT, 19562306a36Sopenharmony_ci .func[YH5151E_PAGE_3V3_LOG] = 19662306a36Sopenharmony_ci PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | 19762306a36Sopenharmony_ci PMBUS_HAVE_POUT, 19862306a36Sopenharmony_ci .read_word_data = fsp3y_read_word_data, 19962306a36Sopenharmony_ci .read_byte_data = fsp3y_read_byte_data, 20062306a36Sopenharmony_ci } 20162306a36Sopenharmony_ci}; 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_cistatic int fsp3y_detect(struct i2c_client *client) 20462306a36Sopenharmony_ci{ 20562306a36Sopenharmony_ci int rv; 20662306a36Sopenharmony_ci u8 buf[I2C_SMBUS_BLOCK_MAX + 1]; 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci rv = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf); 20962306a36Sopenharmony_ci if (rv < 0) 21062306a36Sopenharmony_ci return rv; 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci buf[rv] = '\0'; 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci if (rv == 8) { 21562306a36Sopenharmony_ci if (!strcmp(buf, "YM-2151E")) 21662306a36Sopenharmony_ci return ym2151e; 21762306a36Sopenharmony_ci else if (!strcmp(buf, "YH-5151E")) 21862306a36Sopenharmony_ci return yh5151e; 21962306a36Sopenharmony_ci } 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci dev_err(&client->dev, "Unsupported model %.*s\n", rv, buf); 22262306a36Sopenharmony_ci return -ENODEV; 22362306a36Sopenharmony_ci} 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_cistatic const struct i2c_device_id fsp3y_id[] = { 22662306a36Sopenharmony_ci {"ym2151e", ym2151e}, 22762306a36Sopenharmony_ci {"yh5151e", yh5151e}, 22862306a36Sopenharmony_ci { } 22962306a36Sopenharmony_ci}; 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_cistatic int fsp3y_probe(struct i2c_client *client) 23262306a36Sopenharmony_ci{ 23362306a36Sopenharmony_ci struct fsp3y_data *data; 23462306a36Sopenharmony_ci const struct i2c_device_id *id; 23562306a36Sopenharmony_ci int rv; 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci data = devm_kzalloc(&client->dev, sizeof(struct fsp3y_data), GFP_KERNEL); 23862306a36Sopenharmony_ci if (!data) 23962306a36Sopenharmony_ci return -ENOMEM; 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci data->chip = fsp3y_detect(client); 24262306a36Sopenharmony_ci if (data->chip < 0) 24362306a36Sopenharmony_ci return data->chip; 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci id = i2c_match_id(fsp3y_id, client); 24662306a36Sopenharmony_ci if (data->chip != id->driver_data) 24762306a36Sopenharmony_ci dev_warn(&client->dev, "Device mismatch: Configured %s (%d), detected %d\n", 24862306a36Sopenharmony_ci id->name, (int)id->driver_data, data->chip); 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci rv = i2c_smbus_read_byte_data(client, PMBUS_PAGE); 25162306a36Sopenharmony_ci if (rv < 0) 25262306a36Sopenharmony_ci return rv; 25362306a36Sopenharmony_ci data->page = rv; 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci data->info = fsp3y_info[data->chip]; 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci /* 25862306a36Sopenharmony_ci * YH-5151E sometimes reports vout in linear11 and sometimes in 25962306a36Sopenharmony_ci * linear16. This depends on the exact individual piece of hardware. One 26062306a36Sopenharmony_ci * YH-5151E can use linear16 and another might use linear11 instead. 26162306a36Sopenharmony_ci * 26262306a36Sopenharmony_ci * The format can be recognized by reading VOUT_MODE - if it doesn't 26362306a36Sopenharmony_ci * report a valid exponent, then vout uses linear11. Otherwise, the 26462306a36Sopenharmony_ci * device is compliant and uses linear16. 26562306a36Sopenharmony_ci */ 26662306a36Sopenharmony_ci data->vout_linear_11 = false; 26762306a36Sopenharmony_ci if (data->chip == yh5151e) { 26862306a36Sopenharmony_ci rv = i2c_smbus_read_byte_data(client, PMBUS_VOUT_MODE); 26962306a36Sopenharmony_ci if (rv < 0) 27062306a36Sopenharmony_ci return rv; 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_ci if (rv == 0xFF) 27362306a36Sopenharmony_ci data->vout_linear_11 = true; 27462306a36Sopenharmony_ci } 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci return pmbus_do_probe(client, &data->info); 27762306a36Sopenharmony_ci} 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, fsp3y_id); 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_cistatic struct i2c_driver fsp3y_driver = { 28262306a36Sopenharmony_ci .driver = { 28362306a36Sopenharmony_ci .name = "fsp3y", 28462306a36Sopenharmony_ci }, 28562306a36Sopenharmony_ci .probe = fsp3y_probe, 28662306a36Sopenharmony_ci .id_table = fsp3y_id 28762306a36Sopenharmony_ci}; 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_cimodule_i2c_driver(fsp3y_driver); 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ciMODULE_AUTHOR("Václav Kubernát"); 29262306a36Sopenharmony_ciMODULE_DESCRIPTION("PMBus driver for FSP/3Y-Power power supplies"); 29362306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 29462306a36Sopenharmony_ciMODULE_IMPORT_NS(PMBUS); 295