162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Hardware monitoring driver for PMBus devices 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 2010, 2011 Ericsson AB. 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/kernel.h> 962306a36Sopenharmony_ci#include <linux/module.h> 1062306a36Sopenharmony_ci#include <linux/init.h> 1162306a36Sopenharmony_ci#include <linux/err.h> 1262306a36Sopenharmony_ci#include <linux/slab.h> 1362306a36Sopenharmony_ci#include <linux/mutex.h> 1462306a36Sopenharmony_ci#include <linux/i2c.h> 1562306a36Sopenharmony_ci#include <linux/pmbus.h> 1662306a36Sopenharmony_ci#include "pmbus.h" 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_cistruct pmbus_device_info { 1962306a36Sopenharmony_ci int pages; 2062306a36Sopenharmony_ci u32 flags; 2162306a36Sopenharmony_ci}; 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_cistatic const struct i2c_device_id pmbus_id[]; 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci/* 2662306a36Sopenharmony_ci * Find sensor groups and status registers on each page. 2762306a36Sopenharmony_ci */ 2862306a36Sopenharmony_cistatic void pmbus_find_sensor_groups(struct i2c_client *client, 2962306a36Sopenharmony_ci struct pmbus_driver_info *info) 3062306a36Sopenharmony_ci{ 3162306a36Sopenharmony_ci int page; 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci /* Sensors detected on page 0 only */ 3462306a36Sopenharmony_ci if (pmbus_check_word_register(client, 0, PMBUS_READ_VIN)) 3562306a36Sopenharmony_ci info->func[0] |= PMBUS_HAVE_VIN; 3662306a36Sopenharmony_ci if (pmbus_check_word_register(client, 0, PMBUS_READ_VCAP)) 3762306a36Sopenharmony_ci info->func[0] |= PMBUS_HAVE_VCAP; 3862306a36Sopenharmony_ci if (pmbus_check_word_register(client, 0, PMBUS_READ_IIN)) 3962306a36Sopenharmony_ci info->func[0] |= PMBUS_HAVE_IIN; 4062306a36Sopenharmony_ci if (pmbus_check_word_register(client, 0, PMBUS_READ_PIN)) 4162306a36Sopenharmony_ci info->func[0] |= PMBUS_HAVE_PIN; 4262306a36Sopenharmony_ci if (info->func[0] 4362306a36Sopenharmony_ci && pmbus_check_byte_register(client, 0, PMBUS_STATUS_INPUT)) 4462306a36Sopenharmony_ci info->func[0] |= PMBUS_HAVE_STATUS_INPUT; 4562306a36Sopenharmony_ci if (pmbus_check_byte_register(client, 0, PMBUS_FAN_CONFIG_12) && 4662306a36Sopenharmony_ci pmbus_check_word_register(client, 0, PMBUS_READ_FAN_SPEED_1)) { 4762306a36Sopenharmony_ci info->func[0] |= PMBUS_HAVE_FAN12; 4862306a36Sopenharmony_ci if (pmbus_check_byte_register(client, 0, PMBUS_STATUS_FAN_12)) 4962306a36Sopenharmony_ci info->func[0] |= PMBUS_HAVE_STATUS_FAN12; 5062306a36Sopenharmony_ci } 5162306a36Sopenharmony_ci if (pmbus_check_byte_register(client, 0, PMBUS_FAN_CONFIG_34) && 5262306a36Sopenharmony_ci pmbus_check_word_register(client, 0, PMBUS_READ_FAN_SPEED_3)) { 5362306a36Sopenharmony_ci info->func[0] |= PMBUS_HAVE_FAN34; 5462306a36Sopenharmony_ci if (pmbus_check_byte_register(client, 0, PMBUS_STATUS_FAN_34)) 5562306a36Sopenharmony_ci info->func[0] |= PMBUS_HAVE_STATUS_FAN34; 5662306a36Sopenharmony_ci } 5762306a36Sopenharmony_ci if (pmbus_check_word_register(client, 0, PMBUS_READ_TEMPERATURE_1)) 5862306a36Sopenharmony_ci info->func[0] |= PMBUS_HAVE_TEMP; 5962306a36Sopenharmony_ci if (pmbus_check_word_register(client, 0, PMBUS_READ_TEMPERATURE_2)) 6062306a36Sopenharmony_ci info->func[0] |= PMBUS_HAVE_TEMP2; 6162306a36Sopenharmony_ci if (pmbus_check_word_register(client, 0, PMBUS_READ_TEMPERATURE_3)) 6262306a36Sopenharmony_ci info->func[0] |= PMBUS_HAVE_TEMP3; 6362306a36Sopenharmony_ci if (info->func[0] & (PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 6462306a36Sopenharmony_ci | PMBUS_HAVE_TEMP3) 6562306a36Sopenharmony_ci && pmbus_check_byte_register(client, 0, 6662306a36Sopenharmony_ci PMBUS_STATUS_TEMPERATURE)) 6762306a36Sopenharmony_ci info->func[0] |= PMBUS_HAVE_STATUS_TEMP; 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci /* Sensors detected on all pages */ 7062306a36Sopenharmony_ci for (page = 0; page < info->pages; page++) { 7162306a36Sopenharmony_ci if (pmbus_check_word_register(client, page, PMBUS_READ_VOUT)) { 7262306a36Sopenharmony_ci info->func[page] |= PMBUS_HAVE_VOUT; 7362306a36Sopenharmony_ci if (pmbus_check_byte_register(client, page, 7462306a36Sopenharmony_ci PMBUS_STATUS_VOUT)) 7562306a36Sopenharmony_ci info->func[page] |= PMBUS_HAVE_STATUS_VOUT; 7662306a36Sopenharmony_ci } 7762306a36Sopenharmony_ci if (pmbus_check_word_register(client, page, PMBUS_READ_IOUT)) { 7862306a36Sopenharmony_ci info->func[page] |= PMBUS_HAVE_IOUT; 7962306a36Sopenharmony_ci if (pmbus_check_byte_register(client, 0, 8062306a36Sopenharmony_ci PMBUS_STATUS_IOUT)) 8162306a36Sopenharmony_ci info->func[page] |= PMBUS_HAVE_STATUS_IOUT; 8262306a36Sopenharmony_ci } 8362306a36Sopenharmony_ci if (pmbus_check_word_register(client, page, PMBUS_READ_POUT)) 8462306a36Sopenharmony_ci info->func[page] |= PMBUS_HAVE_POUT; 8562306a36Sopenharmony_ci } 8662306a36Sopenharmony_ci} 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci/* 8962306a36Sopenharmony_ci * Identify chip parameters. 9062306a36Sopenharmony_ci */ 9162306a36Sopenharmony_cistatic int pmbus_identify(struct i2c_client *client, 9262306a36Sopenharmony_ci struct pmbus_driver_info *info) 9362306a36Sopenharmony_ci{ 9462306a36Sopenharmony_ci int ret = 0; 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci if (!info->pages) { 9762306a36Sopenharmony_ci /* 9862306a36Sopenharmony_ci * Check if the PAGE command is supported. If it is, 9962306a36Sopenharmony_ci * keep setting the page number until it fails or until the 10062306a36Sopenharmony_ci * maximum number of pages has been reached. Assume that 10162306a36Sopenharmony_ci * this is the number of pages supported by the chip. 10262306a36Sopenharmony_ci */ 10362306a36Sopenharmony_ci if (pmbus_check_byte_register(client, 0, PMBUS_PAGE)) { 10462306a36Sopenharmony_ci int page; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci for (page = 1; page < PMBUS_PAGES; page++) { 10762306a36Sopenharmony_ci if (pmbus_set_page(client, page, 0xff) < 0) 10862306a36Sopenharmony_ci break; 10962306a36Sopenharmony_ci } 11062306a36Sopenharmony_ci pmbus_set_page(client, 0, 0xff); 11162306a36Sopenharmony_ci info->pages = page; 11262306a36Sopenharmony_ci } else { 11362306a36Sopenharmony_ci info->pages = 1; 11462306a36Sopenharmony_ci } 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci pmbus_clear_faults(client); 11762306a36Sopenharmony_ci } 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci if (pmbus_check_byte_register(client, 0, PMBUS_VOUT_MODE)) { 12062306a36Sopenharmony_ci int vout_mode, i; 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE); 12362306a36Sopenharmony_ci if (vout_mode >= 0 && vout_mode != 0xff) { 12462306a36Sopenharmony_ci switch (vout_mode >> 5) { 12562306a36Sopenharmony_ci case 0: 12662306a36Sopenharmony_ci break; 12762306a36Sopenharmony_ci case 1: 12862306a36Sopenharmony_ci info->format[PSC_VOLTAGE_OUT] = vid; 12962306a36Sopenharmony_ci for (i = 0; i < info->pages; i++) 13062306a36Sopenharmony_ci info->vrm_version[i] = vr11; 13162306a36Sopenharmony_ci break; 13262306a36Sopenharmony_ci case 2: 13362306a36Sopenharmony_ci info->format[PSC_VOLTAGE_OUT] = direct; 13462306a36Sopenharmony_ci break; 13562306a36Sopenharmony_ci default: 13662306a36Sopenharmony_ci ret = -ENODEV; 13762306a36Sopenharmony_ci goto abort; 13862306a36Sopenharmony_ci } 13962306a36Sopenharmony_ci } 14062306a36Sopenharmony_ci } 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci /* 14362306a36Sopenharmony_ci * We should check if the COEFFICIENTS register is supported. 14462306a36Sopenharmony_ci * If it is, and the chip is configured for direct mode, we can read 14562306a36Sopenharmony_ci * the coefficients from the chip, one set per group of sensor 14662306a36Sopenharmony_ci * registers. 14762306a36Sopenharmony_ci * 14862306a36Sopenharmony_ci * To do this, we will need access to a chip which actually supports the 14962306a36Sopenharmony_ci * COEFFICIENTS command, since the command is too complex to implement 15062306a36Sopenharmony_ci * without testing it. Until then, abort if a chip configured for direct 15162306a36Sopenharmony_ci * mode was detected. 15262306a36Sopenharmony_ci */ 15362306a36Sopenharmony_ci if (info->format[PSC_VOLTAGE_OUT] == direct) { 15462306a36Sopenharmony_ci ret = -ENODEV; 15562306a36Sopenharmony_ci goto abort; 15662306a36Sopenharmony_ci } 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci /* Try to find sensor groups */ 15962306a36Sopenharmony_ci pmbus_find_sensor_groups(client, info); 16062306a36Sopenharmony_ciabort: 16162306a36Sopenharmony_ci return ret; 16262306a36Sopenharmony_ci} 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_cistatic int pmbus_probe(struct i2c_client *client) 16562306a36Sopenharmony_ci{ 16662306a36Sopenharmony_ci struct pmbus_driver_info *info; 16762306a36Sopenharmony_ci struct pmbus_platform_data *pdata = NULL; 16862306a36Sopenharmony_ci struct device *dev = &client->dev; 16962306a36Sopenharmony_ci struct pmbus_device_info *device_info; 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci info = devm_kzalloc(dev, sizeof(struct pmbus_driver_info), GFP_KERNEL); 17262306a36Sopenharmony_ci if (!info) 17362306a36Sopenharmony_ci return -ENOMEM; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci device_info = (struct pmbus_device_info *)i2c_match_id(pmbus_id, client)->driver_data; 17662306a36Sopenharmony_ci if (device_info->flags) { 17762306a36Sopenharmony_ci pdata = devm_kzalloc(dev, sizeof(struct pmbus_platform_data), 17862306a36Sopenharmony_ci GFP_KERNEL); 17962306a36Sopenharmony_ci if (!pdata) 18062306a36Sopenharmony_ci return -ENOMEM; 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci pdata->flags = device_info->flags; 18362306a36Sopenharmony_ci } 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci info->pages = device_info->pages; 18662306a36Sopenharmony_ci info->identify = pmbus_identify; 18762306a36Sopenharmony_ci dev->platform_data = pdata; 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci return pmbus_do_probe(client, info); 19062306a36Sopenharmony_ci} 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_cistatic const struct pmbus_device_info pmbus_info_one = { 19362306a36Sopenharmony_ci .pages = 1, 19462306a36Sopenharmony_ci .flags = 0 19562306a36Sopenharmony_ci}; 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_cistatic const struct pmbus_device_info pmbus_info_zero = { 19862306a36Sopenharmony_ci .pages = 0, 19962306a36Sopenharmony_ci .flags = 0 20062306a36Sopenharmony_ci}; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_cistatic const struct pmbus_device_info pmbus_info_one_skip = { 20362306a36Sopenharmony_ci .pages = 1, 20462306a36Sopenharmony_ci .flags = PMBUS_SKIP_STATUS_CHECK 20562306a36Sopenharmony_ci}; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_cistatic const struct pmbus_device_info pmbus_info_one_status = { 20862306a36Sopenharmony_ci .pages = 1, 20962306a36Sopenharmony_ci .flags = PMBUS_READ_STATUS_AFTER_FAILED_CHECK 21062306a36Sopenharmony_ci}; 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci/* 21362306a36Sopenharmony_ci * Use driver_data to set the number of pages supported by the chip. 21462306a36Sopenharmony_ci */ 21562306a36Sopenharmony_cistatic const struct i2c_device_id pmbus_id[] = { 21662306a36Sopenharmony_ci {"adp4000", (kernel_ulong_t)&pmbus_info_one}, 21762306a36Sopenharmony_ci {"bmr310", (kernel_ulong_t)&pmbus_info_one_status}, 21862306a36Sopenharmony_ci {"bmr453", (kernel_ulong_t)&pmbus_info_one}, 21962306a36Sopenharmony_ci {"bmr454", (kernel_ulong_t)&pmbus_info_one}, 22062306a36Sopenharmony_ci {"bmr456", (kernel_ulong_t)&pmbus_info_one}, 22162306a36Sopenharmony_ci {"bmr457", (kernel_ulong_t)&pmbus_info_one}, 22262306a36Sopenharmony_ci {"bmr458", (kernel_ulong_t)&pmbus_info_one_status}, 22362306a36Sopenharmony_ci {"bmr480", (kernel_ulong_t)&pmbus_info_one_status}, 22462306a36Sopenharmony_ci {"bmr490", (kernel_ulong_t)&pmbus_info_one_status}, 22562306a36Sopenharmony_ci {"bmr491", (kernel_ulong_t)&pmbus_info_one_status}, 22662306a36Sopenharmony_ci {"bmr492", (kernel_ulong_t)&pmbus_info_one}, 22762306a36Sopenharmony_ci {"dps460", (kernel_ulong_t)&pmbus_info_one_skip}, 22862306a36Sopenharmony_ci {"dps650ab", (kernel_ulong_t)&pmbus_info_one_skip}, 22962306a36Sopenharmony_ci {"dps800", (kernel_ulong_t)&pmbus_info_one_skip}, 23062306a36Sopenharmony_ci {"max20796", (kernel_ulong_t)&pmbus_info_one}, 23162306a36Sopenharmony_ci {"mdt040", (kernel_ulong_t)&pmbus_info_one}, 23262306a36Sopenharmony_ci {"ncp4200", (kernel_ulong_t)&pmbus_info_one}, 23362306a36Sopenharmony_ci {"ncp4208", (kernel_ulong_t)&pmbus_info_one}, 23462306a36Sopenharmony_ci {"pdt003", (kernel_ulong_t)&pmbus_info_one}, 23562306a36Sopenharmony_ci {"pdt006", (kernel_ulong_t)&pmbus_info_one}, 23662306a36Sopenharmony_ci {"pdt012", (kernel_ulong_t)&pmbus_info_one}, 23762306a36Sopenharmony_ci {"pmbus", (kernel_ulong_t)&pmbus_info_zero}, 23862306a36Sopenharmony_ci {"sgd009", (kernel_ulong_t)&pmbus_info_one_skip}, 23962306a36Sopenharmony_ci {"tps40400", (kernel_ulong_t)&pmbus_info_one}, 24062306a36Sopenharmony_ci {"tps544b20", (kernel_ulong_t)&pmbus_info_one}, 24162306a36Sopenharmony_ci {"tps544b25", (kernel_ulong_t)&pmbus_info_one}, 24262306a36Sopenharmony_ci {"tps544c20", (kernel_ulong_t)&pmbus_info_one}, 24362306a36Sopenharmony_ci {"tps544c25", (kernel_ulong_t)&pmbus_info_one}, 24462306a36Sopenharmony_ci {"udt020", (kernel_ulong_t)&pmbus_info_one}, 24562306a36Sopenharmony_ci {} 24662306a36Sopenharmony_ci}; 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, pmbus_id); 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci/* This is the driver that will be inserted */ 25162306a36Sopenharmony_cistatic struct i2c_driver pmbus_driver = { 25262306a36Sopenharmony_ci .driver = { 25362306a36Sopenharmony_ci .name = "pmbus", 25462306a36Sopenharmony_ci }, 25562306a36Sopenharmony_ci .probe = pmbus_probe, 25662306a36Sopenharmony_ci .id_table = pmbus_id, 25762306a36Sopenharmony_ci}; 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_cimodule_i2c_driver(pmbus_driver); 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ciMODULE_AUTHOR("Guenter Roeck"); 26262306a36Sopenharmony_ciMODULE_DESCRIPTION("Generic PMBus driver"); 26362306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 26462306a36Sopenharmony_ciMODULE_IMPORT_NS(PMBUS); 265