diff options
Diffstat (limited to 'drivers/hwmon/pmbus')
39 files changed, 1144 insertions, 40 deletions
diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index 32d2fc850621..37a5c39784fa 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -56,6 +56,25 @@ config SENSORS_BEL_PFE This driver can also be built as a module. If so, the module will be called bel-pfe. +config SENSORS_BPA_RS600 + tristate "BluTek BPA-RS600 Power Supplies" + help + If you say yes here you get hardware monitoring support for BluTek + BPA-RS600 Power Supplies. + + This driver can also be built as a module. If so, the module will + be called bpa-rs600. + +config SENSORS_FSP_3Y + tristate "FSP/3Y-Power power supplies" + help + If you say yes here you get hardware monitoring support for + FSP/3Y-Power hot-swap power supplies. + Supported models: YH-5151E, YM-2151E + + This driver can also be built as a module. If so, the module will + be called fsp-3y. + config SENSORS_IBM_CFFPS tristate "IBM Common Form Factor Power Supply" depends on LEDS_CLASS @@ -84,6 +103,15 @@ config SENSORS_IR35221 This driver can also be built as a module. If so, the module will be called ir35221. +config SENSORS_IR36021 + tristate "Infineon IR36021" + help + If you say yes here you get hardware monitoring support for Infineon + IR36021. + + This driver can also be built as a module. If so, the module will + be called ir36021. + config SENSORS_IR38064 tristate "Infineon IR38064" help @@ -148,6 +176,15 @@ config SENSORS_LTC3815 This driver can also be built as a module. If so, the module will be called ltc3815. +config SENSORS_MAX15301 + tristate "Maxim MAX15301" + help + If you say yes here you get hardware monitoring support for Maxim + MAX15301, as well as for Flex BMR461. + + This driver can also be built as a module. If so, the module will + be called max15301. + config SENSORS_MAX16064 tristate "Maxim MAX16064" help @@ -247,6 +284,16 @@ config SENSORS_Q54SJ108A2 This driver can also be built as a module. If so, the module will be called q54sj108a2. +config SENSORS_STPDDC60 + tristate "ST STPDDC60" + help + If you say yes here you get hardware monitoring support for ST + STPDDC60 Universal Digital Multicell Controller, as well as for + Flex BMR481. + + This driver can also be built as a module. If so, the module will + be called stpddc60. + config SENSORS_TPS40422 tristate "TI TPS40422" help @@ -257,10 +304,10 @@ config SENSORS_TPS40422 be called tps40422. config SENSORS_TPS53679 - tristate "TI TPS53647, TPS53667, TPS53679, TPS53681, TPS53688" + tristate "TI TPS53647, TPS53667, TPS53676, TPS53679, TPS53681, TPS53688" help If you say yes here you get hardware monitoring support for TI - TPS53647, TPS53667, TPS53679, TPS53681, and TPS53688. + TPS53647, TPS53667, TPS53676, TPS53679, TPS53681, and TPS53688. This driver can also be built as a module. If so, the module will be called tps53679. diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile index 6a4ba0fdc1db..f8dcc27cd56a 100644 --- a/drivers/hwmon/pmbus/Makefile +++ b/drivers/hwmon/pmbus/Makefile @@ -8,15 +8,19 @@ obj-$(CONFIG_SENSORS_PMBUS) += pmbus.o obj-$(CONFIG_SENSORS_ADM1266) += adm1266.o obj-$(CONFIG_SENSORS_ADM1275) += adm1275.o obj-$(CONFIG_SENSORS_BEL_PFE) += bel-pfe.o +obj-$(CONFIG_SENSORS_BPA_RS600) += bpa-rs600.o +obj-$(CONFIG_SENSORS_FSP_3Y) += fsp-3y.o obj-$(CONFIG_SENSORS_IBM_CFFPS) += ibm-cffps.o obj-$(CONFIG_SENSORS_INSPUR_IPSPS) += inspur-ipsps.o obj-$(CONFIG_SENSORS_IR35221) += ir35221.o +obj-$(CONFIG_SENSORS_IR36021) += ir36021.o obj-$(CONFIG_SENSORS_IR38064) += ir38064.o obj-$(CONFIG_SENSORS_IRPS5401) += irps5401.o obj-$(CONFIG_SENSORS_ISL68137) += isl68137.o obj-$(CONFIG_SENSORS_LM25066) += lm25066.o obj-$(CONFIG_SENSORS_LTC2978) += ltc2978.o obj-$(CONFIG_SENSORS_LTC3815) += ltc3815.o +obj-$(CONFIG_SENSORS_MAX15301) += max15301.o obj-$(CONFIG_SENSORS_MAX16064) += max16064.o obj-$(CONFIG_SENSORS_MAX16601) += max16601.o obj-$(CONFIG_SENSORS_MAX20730) += max20730.o @@ -28,6 +32,7 @@ obj-$(CONFIG_SENSORS_MP2975) += mp2975.o obj-$(CONFIG_SENSORS_PM6764TR) += pm6764tr.o obj-$(CONFIG_SENSORS_PXE1610) += pxe1610.o obj-$(CONFIG_SENSORS_Q54SJ108A2) += q54sj108a2.o +obj-$(CONFIG_SENSORS_STPDDC60) += stpddc60.o obj-$(CONFIG_SENSORS_TPS40422) += tps40422.o obj-$(CONFIG_SENSORS_TPS53679) += tps53679.o obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o diff --git a/drivers/hwmon/pmbus/adm1266.c b/drivers/hwmon/pmbus/adm1266.c index 4d2e4ddcfbfd..ec5f932fc6f0 100644 --- a/drivers/hwmon/pmbus/adm1266.c +++ b/drivers/hwmon/pmbus/adm1266.c @@ -510,3 +510,4 @@ module_i2c_driver(adm1266_driver); MODULE_AUTHOR("Alexandru Tachici <alexandru.tachici@analog.com>"); MODULE_DESCRIPTION("PMBus driver for Analog Devices ADM1266"); MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/adm1275.c b/drivers/hwmon/pmbus/adm1275.c index 38a6515b0763..980a3850b2f3 100644 --- a/drivers/hwmon/pmbus/adm1275.c +++ b/drivers/hwmon/pmbus/adm1275.c @@ -805,3 +805,4 @@ module_i2c_driver(adm1275_driver); MODULE_AUTHOR("Guenter Roeck"); MODULE_DESCRIPTION("PMBus driver for Analog Devices ADM1275 and compatibles"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/bel-pfe.c b/drivers/hwmon/pmbus/bel-pfe.c index aed7542d7ce5..4100eefb7ac3 100644 --- a/drivers/hwmon/pmbus/bel-pfe.c +++ b/drivers/hwmon/pmbus/bel-pfe.c @@ -129,3 +129,4 @@ module_i2c_driver(pfe_pmbus_driver); MODULE_AUTHOR("Tao Ren <rentao.bupt@gmail.com>"); MODULE_DESCRIPTION("PMBus driver for BEL PFE Family Power Supplies"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/bpa-rs600.c b/drivers/hwmon/pmbus/bpa-rs600.c new file mode 100644 index 000000000000..f6558ee9dec3 --- /dev/null +++ b/drivers/hwmon/pmbus/bpa-rs600.c @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for BluTek BPA-RS600 Power Supplies + * + * Copyright 2021 Allied Telesis Labs + */ + +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pmbus.h> +#include "pmbus.h" + +#define BPARS600_MFR_VIN_MIN 0xa0 +#define BPARS600_MFR_VIN_MAX 0xa1 +#define BPARS600_MFR_IIN_MAX 0xa2 +#define BPARS600_MFR_PIN_MAX 0xa3 +#define BPARS600_MFR_VOUT_MIN 0xa4 +#define BPARS600_MFR_VOUT_MAX 0xa5 +#define BPARS600_MFR_IOUT_MAX 0xa6 +#define BPARS600_MFR_POUT_MAX 0xa7 + +static int bpa_rs600_read_byte_data(struct i2c_client *client, int page, int reg) +{ + int ret; + + if (page > 0) + return -ENXIO; + + switch (reg) { + case PMBUS_FAN_CONFIG_12: + /* + * Two fans are reported in PMBUS_FAN_CONFIG_12 but there is + * only one fan in the module. Mask out the FAN2 bits. + */ + ret = pmbus_read_byte_data(client, 0, PMBUS_FAN_CONFIG_12); + if (ret >= 0) + ret &= ~(PB_FAN_2_INSTALLED | PB_FAN_2_PULSE_MASK); + break; + default: + ret = -ENODATA; + break; + } + + return ret; +} + +static int bpa_rs600_read_word_data(struct i2c_client *client, int page, int phase, int reg) +{ + int ret; + + if (page > 0) + return -ENXIO; + + switch (reg) { + case PMBUS_VIN_UV_WARN_LIMIT: + ret = pmbus_read_word_data(client, 0, 0xff, BPARS600_MFR_VIN_MIN); + break; + case PMBUS_VIN_OV_WARN_LIMIT: + ret = pmbus_read_word_data(client, 0, 0xff, BPARS600_MFR_VIN_MAX); + break; + case PMBUS_VOUT_UV_WARN_LIMIT: + ret = pmbus_read_word_data(client, 0, 0xff, BPARS600_MFR_VOUT_MIN); + break; + case PMBUS_VOUT_OV_WARN_LIMIT: + ret = pmbus_read_word_data(client, 0, 0xff, BPARS600_MFR_VOUT_MAX); + break; + case PMBUS_IIN_OC_WARN_LIMIT: + ret = pmbus_read_word_data(client, 0, 0xff, BPARS600_MFR_IIN_MAX); + break; + case PMBUS_IOUT_OC_WARN_LIMIT: + ret = pmbus_read_word_data(client, 0, 0xff, BPARS600_MFR_IOUT_MAX); + break; + case PMBUS_PIN_OP_WARN_LIMIT: + ret = pmbus_read_word_data(client, 0, 0xff, BPARS600_MFR_PIN_MAX); + break; + case PMBUS_POUT_OP_WARN_LIMIT: + ret = pmbus_read_word_data(client, 0, 0xff, BPARS600_MFR_POUT_MAX); + break; + case PMBUS_VIN_UV_FAULT_LIMIT: + case PMBUS_VIN_OV_FAULT_LIMIT: + case PMBUS_VOUT_UV_FAULT_LIMIT: + case PMBUS_VOUT_OV_FAULT_LIMIT: + /* These commands return data but it is invalid/un-documented */ + ret = -ENXIO; + break; + default: + if (reg >= PMBUS_VIRT_BASE) + ret = -ENXIO; + else + ret = -ENODATA; + break; + } + + return ret; +} + +static struct pmbus_driver_info bpa_rs600_info = { + .pages = 1, + .format[PSC_VOLTAGE_IN] = linear, + .format[PSC_VOLTAGE_OUT] = linear, + .format[PSC_CURRENT_IN] = linear, + .format[PSC_CURRENT_OUT] = linear, + .format[PSC_POWER] = linear, + .format[PSC_TEMPERATURE] = linear, + .format[PSC_FAN] = linear, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | + PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | + PMBUS_HAVE_PIN | PMBUS_HAVE_POUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | + PMBUS_HAVE_FAN12 | + PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_STATUS_FAN12, + .read_byte_data = bpa_rs600_read_byte_data, + .read_word_data = bpa_rs600_read_word_data, +}; + +static int bpa_rs600_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + u8 buf[I2C_SMBUS_BLOCK_MAX + 1]; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_BYTE_DATA + | I2C_FUNC_SMBUS_READ_WORD_DATA + | I2C_FUNC_SMBUS_READ_BLOCK_DATA)) + return -ENODEV; + + ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf); + if (ret < 0) { + dev_err(dev, "Failed to read Manufacturer Model\n"); + return ret; + } + + if (strncmp(buf, "BPA-RS600", 8)) { + buf[ret] = '\0'; + dev_err(dev, "Unsupported Manufacturer Model '%s'\n", buf); + return -ENODEV; + } + + return pmbus_do_probe(client, &bpa_rs600_info); +} + +static const struct i2c_device_id bpa_rs600_id[] = { + { "bpars600", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, bpa_rs600_id); + +static const struct of_device_id __maybe_unused bpa_rs600_of_match[] = { + { .compatible = "blutek,bpa-rs600" }, + {}, +}; +MODULE_DEVICE_TABLE(of, bpa_rs600_of_match); + +static struct i2c_driver bpa_rs600_driver = { + .driver = { + .name = "bpa-rs600", + .of_match_table = of_match_ptr(bpa_rs600_of_match), + }, + .probe_new = bpa_rs600_probe, + .id_table = bpa_rs600_id, +}; + +module_i2c_driver(bpa_rs600_driver); + +MODULE_AUTHOR("Chris Packham"); +MODULE_DESCRIPTION("PMBus driver for BluTek BPA-RS600"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/fsp-3y.c b/drivers/hwmon/pmbus/fsp-3y.c new file mode 100644 index 000000000000..b177987286ae --- /dev/null +++ b/drivers/hwmon/pmbus/fsp-3y.c @@ -0,0 +1,254 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for FSP 3Y-Power PSUs + * + * Copyright (c) 2021 Václav Kubernát, CESNET + * + * This driver is mostly reverse engineered with the help of a tool called pmbus_peek written by + * David Brownell (and later adopted by Jan Kundrát). The device has some sort of a timing issue + * when switching pages, details are explained in the code. The driver support is limited. It + * exposes only the values, that have been tested to work correctly. Unsupported values either + * aren't supported by the devices or their encondings are unknown. + */ + +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include "pmbus.h" + +#define YM2151_PAGE_12V_LOG 0x00 +#define YM2151_PAGE_12V_REAL 0x00 +#define YM2151_PAGE_5VSB_LOG 0x01 +#define YM2151_PAGE_5VSB_REAL 0x20 +#define YH5151E_PAGE_12V_LOG 0x00 +#define YH5151E_PAGE_12V_REAL 0x00 +#define YH5151E_PAGE_5V_LOG 0x01 +#define YH5151E_PAGE_5V_REAL 0x10 +#define YH5151E_PAGE_3V3_LOG 0x02 +#define YH5151E_PAGE_3V3_REAL 0x11 + +enum chips { + ym2151e, + yh5151e +}; + +struct fsp3y_data { + struct pmbus_driver_info info; + int chip; + int page; +}; + +#define to_fsp3y_data(x) container_of(x, struct fsp3y_data, info) + +static int page_log_to_page_real(int page_log, enum chips chip) +{ + switch (chip) { + case ym2151e: + switch (page_log) { + case YM2151_PAGE_12V_LOG: + return YM2151_PAGE_12V_REAL; + case YM2151_PAGE_5VSB_LOG: + return YM2151_PAGE_5VSB_REAL; + } + return -EINVAL; + case yh5151e: + switch (page_log) { + case YH5151E_PAGE_12V_LOG: + return YH5151E_PAGE_12V_REAL; + case YH5151E_PAGE_5V_LOG: + return YH5151E_PAGE_5V_LOG; + case YH5151E_PAGE_3V3_LOG: + return YH5151E_PAGE_3V3_REAL; + } + return -EINVAL; + } + + return -EINVAL; +} + +static int set_page(struct i2c_client *client, int page_log) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct fsp3y_data *data = to_fsp3y_data(info); + int rv; + int page_real; + + if (page_log < 0) + return 0; + + page_real = page_log_to_page_real(page_log, data->chip); + if (page_real < 0) + return page_real; + + if (data->page != page_real) { + rv = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page_real); + if (rv < 0) + return rv; + + data->page = page_real; + + /* + * Testing showed that the device has a timing issue. After + * setting a page, it takes a while, before the device actually + * gives the correct values from the correct page. 20 ms was + * tested to be enough to not give wrong values (15 ms wasn't + * enough). + */ + usleep_range(20000, 30000); + } + + return 0; +} + +static int fsp3y_read_byte_data(struct i2c_client *client, int page, int reg) +{ + int rv; + + rv = set_page(client, page); + if (rv < 0) + return rv; + + return i2c_smbus_read_byte_data(client, reg); +} + +static int fsp3y_read_word_data(struct i2c_client *client, int page, int phase, int reg) +{ + int rv; + + /* + * This masks commands which weren't tested to work correctly. Some of + * the masked commands return 0xFFFF. These would probably get tagged as + * invalid by pmbus_core. Other ones do return values which might be + * useful (that is, they are not 0xFFFF), but their encoding is unknown, + * and so they are unsupported. + */ + switch (reg) { + case PMBUS_READ_FAN_SPEED_1: + case PMBUS_READ_IIN: + case PMBUS_READ_IOUT: + case PMBUS_READ_PIN: + case PMBUS_READ_POUT: + case PMBUS_READ_TEMPERATURE_1: + case PMBUS_READ_TEMPERATURE_2: + case PMBUS_READ_TEMPERATURE_3: + case PMBUS_READ_VIN: + case PMBUS_READ_VOUT: + case PMBUS_STATUS_WORD: + break; + default: + return -ENXIO; + } + + rv = set_page(client, page); + if (rv < 0) + return rv; + + return i2c_smbus_read_word_data(client, reg); +} + +static struct pmbus_driver_info fsp3y_info[] = { + [ym2151e] = { + .pages = 2, + .func[YM2151_PAGE_12V_LOG] = + PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | + PMBUS_HAVE_PIN | PMBUS_HAVE_POUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | + PMBUS_HAVE_VIN | PMBUS_HAVE_IIN | + PMBUS_HAVE_FAN12, + .func[YM2151_PAGE_5VSB_LOG] = + PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT, + PMBUS_HAVE_IIN, + .read_word_data = fsp3y_read_word_data, + .read_byte_data = fsp3y_read_byte_data, + }, + [yh5151e] = { + .pages = 3, + .func[YH5151E_PAGE_12V_LOG] = + PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | + PMBUS_HAVE_POUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_TEMP3, + .func[YH5151E_PAGE_5V_LOG] = + PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | + PMBUS_HAVE_POUT, + .func[YH5151E_PAGE_3V3_LOG] = + PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | + PMBUS_HAVE_POUT, + .read_word_data = fsp3y_read_word_data, + .read_byte_data = fsp3y_read_byte_data, + } +}; + +static int fsp3y_detect(struct i2c_client *client) +{ + int rv; + u8 buf[I2C_SMBUS_BLOCK_MAX + 1]; + + rv = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf); + if (rv < 0) + return rv; + + buf[rv] = '\0'; + + if (rv == 8) { + if (!strcmp(buf, "YM-2151E")) + return ym2151e; + else if (!strcmp(buf, "YH-5151E")) + return yh5151e; + } + + dev_err(&client->dev, "Unsupported model %.*s\n", rv, buf); + return -ENODEV; +} + +static const struct i2c_device_id fsp3y_id[] = { + {"ym2151e", ym2151e}, + {"yh5151e", yh5151e}, + { } +}; + +static int fsp3y_probe(struct i2c_client *client) +{ + struct fsp3y_data *data; + const struct i2c_device_id *id; + int rv; + + data = devm_kzalloc(&client->dev, sizeof(struct fsp3y_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->chip = fsp3y_detect(client); + if (data->chip < 0) + return data->chip; + + id = i2c_match_id(fsp3y_id, client); + if (data->chip != id->driver_data) + dev_warn(&client->dev, "Device mismatch: Configured %s (%d), detected %d\n", + id->name, (int)id->driver_data, data->chip); + + rv = i2c_smbus_read_byte_data(client, PMBUS_PAGE); + if (rv < 0) + return rv; + data->page = rv; + + data->info = fsp3y_info[data->chip]; + + return pmbus_do_probe(client, &data->info); +} + +MODULE_DEVICE_TABLE(i2c, fsp3y_id); + +static struct i2c_driver fsp3y_driver = { + .driver = { + .name = "fsp3y", + }, + .probe_new = fsp3y_probe, + .id_table = fsp3y_id +}; + +module_i2c_driver(fsp3y_driver); + +MODULE_AUTHOR("Václav Kubernát"); +MODULE_DESCRIPTION("PMBus driver for FSP/3Y-Power power supplies"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/ibm-cffps.c b/drivers/hwmon/pmbus/ibm-cffps.c index ffde5aaa5036..5668d8305b78 100644 --- a/drivers/hwmon/pmbus/ibm-cffps.c +++ b/drivers/hwmon/pmbus/ibm-cffps.c @@ -625,3 +625,4 @@ module_i2c_driver(ibm_cffps_driver); MODULE_AUTHOR("Eddie James"); MODULE_DESCRIPTION("PMBus driver for IBM Common Form Factor power supplies"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/inspur-ipsps.c b/drivers/hwmon/pmbus/inspur-ipsps.c index 88c5865c4d6f..0f614e8d95f6 100644 --- a/drivers/hwmon/pmbus/inspur-ipsps.c +++ b/drivers/hwmon/pmbus/inspur-ipsps.c @@ -70,7 +70,7 @@ static ssize_t ipsps_string_show(struct device *dev, p = memscan(data, '#', rc); *p = '\0'; - return snprintf(buf, PAGE_SIZE, "%s\n", data); + return sysfs_emit(buf, "%s\n", data); } static ssize_t ipsps_fw_version_show(struct device *dev, @@ -91,9 +91,9 @@ static ssize_t ipsps_fw_version_show(struct device *dev, if (rc != 6) return -EPROTO; - return snprintf(buf, PAGE_SIZE, "%u.%02u%u-%u.%02u\n", - data[1], data[2]/* < 100 */, data[3]/*< 10*/, - data[4], data[5]/* < 100 */); + return sysfs_emit(buf, "%u.%02u%u-%u.%02u\n", + data[1], data[2]/* < 100 */, data[3]/*< 10*/, + data[4], data[5]/* < 100 */); } static ssize_t ipsps_mode_show(struct device *dev, @@ -111,19 +111,19 @@ static ssize_t ipsps_mode_show(struct device *dev, switch (rc) { case MODE_ACTIVE: - return snprintf(buf, PAGE_SIZE, "[%s] %s %s\n", - MODE_ACTIVE_STRING, - MODE_STANDBY_STRING, MODE_REDUNDANCY_STRING); + return sysfs_emit(buf, "[%s] %s %s\n", + MODE_ACTIVE_STRING, + MODE_STANDBY_STRING, MODE_REDUNDANCY_STRING); case MODE_STANDBY: - return snprintf(buf, PAGE_SIZE, "%s [%s] %s\n", - MODE_ACTIVE_STRING, - MODE_STANDBY_STRING, MODE_REDUNDANCY_STRING); + return sysfs_emit(buf, "%s [%s] %s\n", + MODE_ACTIVE_STRING, + MODE_STANDBY_STRING, MODE_REDUNDANCY_STRING); case MODE_REDUNDANCY: - return snprintf(buf, PAGE_SIZE, "%s %s [%s]\n", - MODE_ACTIVE_STRING, - MODE_STANDBY_STRING, MODE_REDUNDANCY_STRING); + return sysfs_emit(buf, "%s %s [%s]\n", + MODE_ACTIVE_STRING, + MODE_STANDBY_STRING, MODE_REDUNDANCY_STRING); default: - return snprintf(buf, PAGE_SIZE, "unspecified\n"); + return sysfs_emit(buf, "unspecified\n"); } } @@ -224,3 +224,4 @@ module_i2c_driver(ipsps_driver); MODULE_AUTHOR("John Wang"); MODULE_DESCRIPTION("PMBus driver for Inspur Power System power supplies"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/ir35221.c b/drivers/hwmon/pmbus/ir35221.c index 3aebeb1443fd..a6cf98e49666 100644 --- a/drivers/hwmon/pmbus/ir35221.c +++ b/drivers/hwmon/pmbus/ir35221.c @@ -145,3 +145,4 @@ module_i2c_driver(ir35221_driver); MODULE_AUTHOR("Samuel Mendoza-Jonas <sam@mendozajonas.com"); MODULE_DESCRIPTION("PMBus driver for IR35221"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/ir36021.c b/drivers/hwmon/pmbus/ir36021.c new file mode 100644 index 000000000000..4dca4767f571 --- /dev/null +++ b/drivers/hwmon/pmbus/ir36021.c @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Hardware monitoring driver for Infineon IR36021 + * + * Copyright (c) 2021 Allied Telesis + */ +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include "pmbus.h" + +static struct pmbus_driver_info ir36021_info = { + .pages = 1, + .format[PSC_VOLTAGE_IN] = linear, + .format[PSC_VOLTAGE_OUT] = linear, + .format[PSC_CURRENT_IN] = linear, + .format[PSC_CURRENT_OUT] = linear, + .format[PSC_POWER] = linear, + .format[PSC_TEMPERATURE] = linear, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT + | PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT + | PMBUS_HAVE_PIN | PMBUS_HAVE_POUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 + | PMBUS_HAVE_STATUS_TEMP, +}; + +static int ir36021_probe(struct i2c_client *client) +{ + u8 buf[I2C_SMBUS_BLOCK_MAX]; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_BYTE_DATA + | I2C_FUNC_SMBUS_READ_WORD_DATA + | I2C_FUNC_SMBUS_READ_BLOCK_DATA)) + return -ENODEV; + + ret = i2c_smbus_read_i2c_block_data(client, PMBUS_MFR_MODEL, 2, buf); + if (ret < 0) { + dev_err(&client->dev, "Failed to read PMBUS_MFR_MODEL\n"); + return ret; + } + if (ret != 2 || buf[0] != 0x01 || buf[1] != 0x2d) { + dev_err(&client->dev, "MFR_MODEL unrecognised\n"); + return -ENODEV; + } + + return pmbus_do_probe(client, &ir36021_info); +} + +static const struct i2c_device_id ir36021_id[] = { + { "ir36021", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, ir36021_id); + +static const struct of_device_id __maybe_unused ir36021_of_id[] = { + { .compatible = "infineon,ir36021" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ir36021_of_id); + +static struct i2c_driver ir36021_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "ir36021", + .of_match_table = of_match_ptr(ir36021_of_id), + }, + .probe_new = ir36021_probe, + .id_table = ir36021_id, +}; + +module_i2c_driver(ir36021_driver); + +MODULE_AUTHOR("Chris Packham <chris.packham@alliedtelesis.co.nz>"); +MODULE_DESCRIPTION("PMBus driver for Infineon IR36021"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/ir38064.c b/drivers/hwmon/pmbus/ir38064.c index 46f17c4b4873..1fb7f1248639 100644 --- a/drivers/hwmon/pmbus/ir38064.c +++ b/drivers/hwmon/pmbus/ir38064.c @@ -61,3 +61,4 @@ module_i2c_driver(ir38064_driver); MODULE_AUTHOR("Maxim Sloyko <maxims@google.com>"); MODULE_DESCRIPTION("PMBus driver for Infineon IR38064"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/irps5401.c b/drivers/hwmon/pmbus/irps5401.c index 93ef6d64a33a..de3449e4d77a 100644 --- a/drivers/hwmon/pmbus/irps5401.c +++ b/drivers/hwmon/pmbus/irps5401.c @@ -63,3 +63,4 @@ module_i2c_driver(irps5401_driver); MODULE_AUTHOR("Robert Hancock"); MODULE_DESCRIPTION("PMBus driver for Infineon IRPS5401"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/isl68137.c b/drivers/hwmon/pmbus/isl68137.c index 2bee930d3900..40597a9e799f 100644 --- a/drivers/hwmon/pmbus/isl68137.c +++ b/drivers/hwmon/pmbus/isl68137.c @@ -332,3 +332,4 @@ module_i2c_driver(isl68137_driver); MODULE_AUTHOR("Maxim Sloyko <maxims@google.com>"); MODULE_DESCRIPTION("PMBus driver for Renesas digital multiphase voltage regulators"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/lm25066.c b/drivers/hwmon/pmbus/lm25066.c index e9a66fd9e144..d209e0afc2ca 100644 --- a/drivers/hwmon/pmbus/lm25066.c +++ b/drivers/hwmon/pmbus/lm25066.c @@ -511,3 +511,4 @@ module_i2c_driver(lm25066_driver); MODULE_AUTHOR("Guenter Roeck"); MODULE_DESCRIPTION("PMBus driver for LM25066 and compatible chips"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/ltc2978.c b/drivers/hwmon/pmbus/ltc2978.c index 7e53fa95b92d..0127273883f0 100644 --- a/drivers/hwmon/pmbus/ltc2978.c +++ b/drivers/hwmon/pmbus/ltc2978.c @@ -883,3 +883,4 @@ module_i2c_driver(ltc2978_driver); MODULE_AUTHOR("Guenter Roeck"); MODULE_DESCRIPTION("PMBus driver for LTC2978 and compatible chips"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/ltc3815.c b/drivers/hwmon/pmbus/ltc3815.c index e45e14d26c9a..8e13a7ddcb42 100644 --- a/drivers/hwmon/pmbus/ltc3815.c +++ b/drivers/hwmon/pmbus/ltc3815.c @@ -208,3 +208,4 @@ module_i2c_driver(ltc3815_driver); MODULE_AUTHOR("Guenter Roeck"); MODULE_DESCRIPTION("PMBus driver for LTC3815"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/max15301.c b/drivers/hwmon/pmbus/max15301.c new file mode 100644 index 000000000000..0b6f88428ea8 --- /dev/null +++ b/drivers/hwmon/pmbus/max15301.c @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for Maxim MAX15301 + * + * Copyright (c) 2021 Flextronics International Sweden AB + * + * Even though the specification does not specifically mention it, + * extensive empirical testing has revealed that auto-detection of + * limit-registers will fail in a random fashion unless the delay + * parameter is set to above about 80us. The default delay is set + * to 100us to include some safety margin. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/ktime.h> +#include <linux/delay.h> +#include <linux/pmbus.h> +#include "pmbus.h" + +static const struct i2c_device_id max15301_id[] = { + {"bmr461", 0}, + {"max15301", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, max15301_id); + +struct max15301_data { + int id; + ktime_t access; /* Chip access time */ + int delay; /* Delay between chip accesses in us */ + struct pmbus_driver_info info; +}; + +#define to_max15301_data(x) container_of(x, struct max15301_data, info) + +#define MAX15301_WAIT_TIME 100 /* us */ + +static ushort delay = MAX15301_WAIT_TIME; +module_param(delay, ushort, 0644); +MODULE_PARM_DESC(delay, "Delay between chip accesses in us"); + +static struct max15301_data max15301_data = { + .info = { + .pages = 1, + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 + | PMBUS_HAVE_STATUS_TEMP + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + } +}; + +/* This chip needs a delay between accesses */ +static inline void max15301_wait(const struct max15301_data *data) +{ + if (data->delay) { + s64 delta = ktime_us_delta(ktime_get(), data->access); + + if (delta < data->delay) + udelay(data->delay - delta); + } +} + +static int max15301_read_word_data(struct i2c_client *client, int page, + int phase, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct max15301_data *data = to_max15301_data(info); + int ret; + + if (page > 0) + return -ENXIO; + + if (reg >= PMBUS_VIRT_BASE) + return -ENXIO; + + max15301_wait(data); + ret = pmbus_read_word_data(client, page, phase, reg); + data->access = ktime_get(); + + return ret; +} + +static int max15301_read_byte_data(struct i2c_client *client, int page, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct max15301_data *data = to_max15301_data(info); + int ret; + + if (page > 0) + return -ENXIO; + + max15301_wait(data); + ret = pmbus_read_byte_data(client, page, reg); + data->access = ktime_get(); + + return ret; +} + +static int max15301_write_word_data(struct i2c_client *client, int page, int reg, + u16 word) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct max15301_data *data = to_max15301_data(info); + int ret; + + if (page > 0) + return -ENXIO; + + if (reg >= PMBUS_VIRT_BASE) + return -ENXIO; + + max15301_wait(data); + ret = pmbus_write_word_data(client, page, reg, word); + data->access = ktime_get(); + + return ret; +} + +static int max15301_write_byte(struct i2c_client *client, int page, u8 value) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct max15301_data *data = to_max15301_data(info); + int ret; + + if (page > 0) + return -ENXIO; + + max15301_wait(data); + ret = pmbus_write_byte(client, page, value); + data->access = ktime_get(); + + return ret; +} + +static int max15301_probe(struct i2c_client *client) +{ + int status; + u8 device_id[I2C_SMBUS_BLOCK_MAX + 1]; + const struct i2c_device_id *mid; + struct pmbus_driver_info *info = &max15301_data.info; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_BYTE_DATA + | I2C_FUNC_SMBUS_BLOCK_DATA)) + return -ENODEV; + + status = i2c_smbus_read_block_data(client, PMBUS_IC_DEVICE_ID, device_id); + if (status < 0) { + dev_err(&client->dev, "Failed to read Device Id\n"); + return status; + } + for (mid = max15301_id; mid->name[0]; mid++) { + if (!strncasecmp(mid->name, device_id, strlen(mid->name))) + break; + } + if (!mid->name[0]) { + dev_err(&client->dev, "Unsupported device\n"); + return -ENODEV; + } + + max15301_data.delay = delay; + + info->read_byte_data = max15301_read_byte_data; + info->read_word_data = max15301_read_word_data; + info->write_byte = max15301_write_byte; + info->write_word_data = max15301_write_word_data; + + return pmbus_do_probe(client, info); +} + +static struct i2c_driver max15301_driver = { + .driver = { + .name = "max15301", + }, + .probe_new = max15301_probe, + .id_table = max15301_id, +}; + +module_i2c_driver(max15301_driver); + +MODULE_AUTHOR("Erik Rosen <erik.rosen@metormote.com>"); +MODULE_DESCRIPTION("PMBus driver for Maxim MAX15301"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/max16064.c b/drivers/hwmon/pmbus/max16064.c index d79add99083e..94f869039071 100644 --- a/drivers/hwmon/pmbus/max16064.c +++ b/drivers/hwmon/pmbus/max16064.c @@ -111,3 +111,4 @@ module_i2c_driver(max16064_driver); MODULE_AUTHOR("Guenter Roeck"); MODULE_DESCRIPTION("PMBus driver for Maxim MAX16064"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/max16601.c b/drivers/hwmon/pmbus/max16601.c index 0d1204c2dd54..5a226a564776 100644 --- a/drivers/hwmon/pmbus/max16601.c +++ b/drivers/hwmon/pmbus/max16601.c @@ -359,3 +359,4 @@ module_i2c_driver(max16601_driver); MODULE_AUTHOR("Guenter Roeck <linux@roeck-us.net>"); MODULE_DESCRIPTION("PMBus driver for Maxim MAX16601"); MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/max20730.c b/drivers/hwmon/pmbus/max20730.c index 9dd3dd79bc18..ba39f03c6374 100644 --- a/drivers/hwmon/pmbus/max20730.c +++ b/drivers/hwmon/pmbus/max20730.c @@ -785,3 +785,4 @@ module_i2c_driver(max20730_driver); MODULE_AUTHOR("Guenter Roeck <linux@roeck-us.net>"); MODULE_DESCRIPTION("PMBus driver for Maxim MAX20710 / MAX20730 / MAX20734 / MAX20743"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/max20751.c b/drivers/hwmon/pmbus/max20751.c index 9d42f82fdd99..2272dc8c2e38 100644 --- a/drivers/hwmon/pmbus/max20751.c +++ b/drivers/hwmon/pmbus/max20751.c @@ -51,3 +51,4 @@ module_i2c_driver(max20751_driver); MODULE_AUTHOR("Guenter Roeck <linux@roeck-us.net>"); MODULE_DESCRIPTION("PMBus driver for Maxim MAX20751"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/max31785.c b/drivers/hwmon/pmbus/max31785.c index 17489abc49d5..95d79a64b483 100644 --- a/drivers/hwmon/pmbus/max31785.c +++ b/drivers/hwmon/pmbus/max31785.c @@ -403,3 +403,4 @@ module_i2c_driver(max31785_driver); MODULE_AUTHOR("Andrew Jeffery <andrew@aj.id.au>"); MODULE_DESCRIPTION("PMBus driver for the Maxim MAX31785"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/max34440.c b/drivers/hwmon/pmbus/max34440.c index dad66b3c0116..ea7609058a12 100644 --- a/drivers/hwmon/pmbus/max34440.c +++ b/drivers/hwmon/pmbus/max34440.c @@ -529,3 +529,4 @@ module_i2c_driver(max34440_driver); MODULE_AUTHOR("Guenter Roeck"); MODULE_DESCRIPTION("PMBus driver for Maxim MAX34440/MAX34441"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/max8688.c b/drivers/hwmon/pmbus/max8688.c index 329dc851fc59..5e66c28c0b71 100644 --- a/drivers/hwmon/pmbus/max8688.c +++ b/drivers/hwmon/pmbus/max8688.c @@ -191,3 +191,4 @@ module_i2c_driver(max8688_driver); MODULE_AUTHOR("Guenter Roeck"); MODULE_DESCRIPTION("PMBus driver for Maxim MAX8688"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/mp2975.c b/drivers/hwmon/pmbus/mp2975.c index 60fbdb371332..eb94bd5f4e2a 100644 --- a/drivers/hwmon/pmbus/mp2975.c +++ b/drivers/hwmon/pmbus/mp2975.c @@ -766,3 +766,4 @@ module_i2c_driver(mp2975_driver); MODULE_AUTHOR("Vadim Pasternak <vadimp@nvidia.com>"); MODULE_DESCRIPTION("PMBus driver for MPS MP2975 device"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/pm6764tr.c b/drivers/hwmon/pmbus/pm6764tr.c index d97cb6d6c87f..e0bbc8a30d21 100644 --- a/drivers/hwmon/pmbus/pm6764tr.c +++ b/drivers/hwmon/pmbus/pm6764tr.c @@ -73,3 +73,4 @@ module_i2c_driver(pm6764tr_driver); MODULE_AUTHOR("Charles Hsu"); MODULE_DESCRIPTION("PMBus driver for ST PM6764TR"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/pmbus.c b/drivers/hwmon/pmbus/pmbus.c index a1b4260e75b2..618c377664c4 100644 --- a/drivers/hwmon/pmbus/pmbus.c +++ b/drivers/hwmon/pmbus/pmbus.c @@ -246,3 +246,4 @@ module_i2c_driver(pmbus_driver); MODULE_AUTHOR("Guenter Roeck"); MODULE_DESCRIPTION("Generic PMBus driver"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h index 4c30ec89f5bf..3968924f8533 100644 --- a/drivers/hwmon/pmbus/pmbus.h +++ b/drivers/hwmon/pmbus/pmbus.h @@ -475,6 +475,7 @@ extern const struct regulator_ops pmbus_regulator_ops; /* Function declarations */ void pmbus_clear_cache(struct i2c_client *client); +void pmbus_set_update(struct i2c_client *client, u8 reg, bool update); int pmbus_set_page(struct i2c_client *client, int page, int phase); int pmbus_read_word_data(struct i2c_client *client, int page, int phase, u8 reg); diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index aadea85fe630..bbd745178147 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -139,7 +139,18 @@ void pmbus_clear_cache(struct i2c_client *client) for (sensor = data->sensors; sensor; sensor = sensor->next) sensor->data = -ENODATA; } -EXPORT_SYMBOL_GPL(pmbus_clear_cache); +EXPORT_SYMBOL_NS_GPL(pmbus_clear_cache, PMBUS); + +void pmbus_set_update(struct i2c_client *client, u8 reg, bool update) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + struct pmbus_sensor *sensor; + + for (sensor = data->sensors; sensor; sensor = sensor->next) + if (sensor->reg == reg) + sensor->update = update; +} +EXPORT_SYMBOL_NS_GPL(pmbus_set_update, PMBUS); int pmbus_set_page(struct i2c_client *client, int page, int phase) { @@ -175,7 +186,7 @@ int pmbus_set_page(struct i2c_client *client, int page, int phase) return 0; } -EXPORT_SYMBOL_GPL(pmbus_set_page); +EXPORT_SYMBOL_NS_GPL(pmbus_set_page, PMBUS); int pmbus_write_byte(struct i2c_client *client, int page, u8 value) { @@ -187,7 +198,7 @@ int pmbus_write_byte(struct i2c_client *client, int page, u8 value) return i2c_smbus_write_byte(client, value); } -EXPORT_SYMBOL_GPL(pmbus_write_byte); +EXPORT_SYMBOL_NS_GPL(pmbus_write_byte, PMBUS); /* * _pmbus_write_byte() is similar to pmbus_write_byte(), but checks if @@ -218,7 +229,7 @@ int pmbus_write_word_data(struct i2c_client *client, int page, u8 reg, return i2c_smbus_write_word_data(client, reg, word); } -EXPORT_SYMBOL_GPL(pmbus_write_word_data); +EXPORT_SYMBOL_NS_GPL(pmbus_write_word_data, PMBUS); static int pmbus_write_virt_reg(struct i2c_client *client, int page, int reg, @@ -288,7 +299,7 @@ int pmbus_update_fan(struct i2c_client *client, int page, int id, return _pmbus_write_word_data(client, page, pmbus_fan_command_registers[id], command); } -EXPORT_SYMBOL_GPL(pmbus_update_fan); +EXPORT_SYMBOL_NS_GPL(pmbus_update_fan, PMBUS); int pmbus_read_word_data(struct i2c_client *client, int page, int phase, u8 reg) { @@ -300,7 +311,7 @@ int pmbus_read_word_data(struct i2c_client *client, int page, int phase, u8 reg) return i2c_smbus_read_word_data(client, reg); } -EXPORT_SYMBOL_GPL(pmbus_read_word_data); +EXPORT_SYMBOL_NS_GPL(pmbus_read_word_data, PMBUS); static int pmbus_read_virt_reg(struct i2c_client *client, int page, int reg) { @@ -359,7 +370,7 @@ int pmbus_read_byte_data(struct i2c_client *client, int page, u8 reg) return i2c_smbus_read_byte_data(client, reg); } -EXPORT_SYMBOL_GPL(pmbus_read_byte_data); +EXPORT_SYMBOL_NS_GPL(pmbus_read_byte_data, PMBUS); int pmbus_write_byte_data(struct i2c_client *client, int page, u8 reg, u8 value) { @@ -371,7 +382,7 @@ int pmbus_write_byte_data(struct i2c_client *client, int page, u8 reg, u8 value) return i2c_smbus_write_byte_data(client, reg, value); } -EXPORT_SYMBOL_GPL(pmbus_write_byte_data); +EXPORT_SYMBOL_NS_GPL(pmbus_write_byte_data, PMBUS); int pmbus_update_byte_data(struct i2c_client *client, int page, u8 reg, u8 mask, u8 value) @@ -390,7 +401,7 @@ int pmbus_update_byte_data(struct i2c_client *client, int page, u8 reg, return rv; } -EXPORT_SYMBOL_GPL(pmbus_update_byte_data); +EXPORT_SYMBOL_NS_GPL(pmbus_update_byte_data, PMBUS); /* * _pmbus_read_byte_data() is similar to pmbus_read_byte_data(), but checks if @@ -463,14 +474,14 @@ int pmbus_get_fan_rate_device(struct i2c_client *client, int page, int id, { return pmbus_get_fan_rate(client, page, id, mode, false); } -EXPORT_SYMBOL_GPL(pmbus_get_fan_rate_device); +EXPORT_SYMBOL_NS_GPL(pmbus_get_fan_rate_device, PMBUS); int pmbus_get_fan_rate_cached(struct i2c_client *client, int page, int id, enum pmbus_fan_mode mode) { return pmbus_get_fan_rate(client, page, id, mode, true); } -EXPORT_SYMBOL_GPL(pmbus_get_fan_rate_cached); +EXPORT_SYMBOL_NS_GPL(pmbus_get_fan_rate_cached, PMBUS); static void pmbus_clear_fault_page(struct i2c_client *client, int page) { @@ -485,7 +496,7 @@ void pmbus_clear_faults(struct i2c_client *client) for (i = 0; i < data->info->pages; i++) pmbus_clear_fault_page(client, i); } -EXPORT_SYMBOL_GPL(pmbus_clear_faults); +EXPORT_SYMBOL_NS_GPL(pmbus_clear_faults, PMBUS); static int pmbus_check_status_cml(struct i2c_client *client) { @@ -537,13 +548,13 @@ bool pmbus_check_byte_register(struct i2c_client *client, int page, int reg) { return pmbus_check_register(client, _pmbus_read_byte_data, page, reg); } -EXPORT_SYMBOL_GPL(pmbus_check_byte_register); +EXPORT_SYMBOL_NS_GPL(pmbus_check_byte_register, PMBUS); bool pmbus_check_word_register(struct i2c_client *client, int page, int reg) { return pmbus_check_register(client, __pmbus_read_word_data, page, reg); } -EXPORT_SYMBOL_GPL(pmbus_check_word_register); +EXPORT_SYMBOL_NS_GPL(pmbus_check_word_register, PMBUS); const struct pmbus_driver_info *pmbus_get_driver_info(struct i2c_client *client) { @@ -551,7 +562,7 @@ const struct pmbus_driver_info *pmbus_get_driver_info(struct i2c_client *client) return data->info; } -EXPORT_SYMBOL_GPL(pmbus_get_driver_info); +EXPORT_SYMBOL_NS_GPL(pmbus_get_driver_info, PMBUS); static int pmbus_get_status(struct i2c_client *client, int page, int reg) { @@ -932,7 +943,7 @@ static ssize_t pmbus_show_boolean(struct device *dev, val = pmbus_get_boolean(client, boolean, attr->index); if (val < 0) return val; - return snprintf(buf, PAGE_SIZE, "%d\n", val); + return sysfs_emit(buf, "%d\n", val); } static ssize_t pmbus_show_sensor(struct device *dev, @@ -948,7 +959,7 @@ static ssize_t pmbus_show_sensor(struct device *dev, if (sensor->data < 0) ret = sensor->data; else - ret = snprintf(buf, PAGE_SIZE, "%lld\n", pmbus_reg2data(data, sensor)); + ret = sysfs_emit(buf, "%lld\n", pmbus_reg2data(data, sensor)); mutex_unlock(&data->update_lock); return ret; } @@ -984,7 +995,7 @@ static ssize_t pmbus_show_label(struct device *dev, { struct pmbus_label *label = to_pmbus_label(da); - return snprintf(buf, PAGE_SIZE, "%s\n", label->label); + return sysfs_emit(buf, "%s\n", label->label); } static int pmbus_add_attribute(struct pmbus_data *data, struct attribute *attr) @@ -2024,7 +2035,7 @@ static ssize_t pmbus_show_samples(struct device *dev, if (val < 0) return val; - return snprintf(buf, PAGE_SIZE, "%d\n", val); + return sysfs_emit(buf, "%d\n", val); } static ssize_t pmbus_set_samples(struct device *dev, @@ -2288,7 +2299,7 @@ const struct regulator_ops pmbus_regulator_ops = { .disable = pmbus_regulator_disable, .is_enabled = pmbus_regulator_is_enabled, }; -EXPORT_SYMBOL_GPL(pmbus_regulator_ops); +EXPORT_SYMBOL_NS_GPL(pmbus_regulator_ops, PMBUS); static int pmbus_regulator_register(struct pmbus_data *data) { @@ -2557,6 +2568,7 @@ int pmbus_do_probe(struct i2c_client *client, struct pmbus_driver_info *info) struct pmbus_data *data; size_t groups_num = 0; int ret; + char *name; if (!info) return -ENODEV; @@ -2606,10 +2618,15 @@ int pmbus_do_probe(struct i2c_client *client, struct pmbus_driver_info *info) return -ENODEV; } + name = devm_kstrdup(dev, client->name, GFP_KERNEL); + if (!name) + return -ENOMEM; + strreplace(name, '-', '_'); + data->groups[0] = &data->group; memcpy(data->groups + 1, info->groups, sizeof(void *) * groups_num); data->hwmon_dev = devm_hwmon_device_register_with_groups(dev, - client->name, data, data->groups); + name, data, data->groups); if (IS_ERR(data->hwmon_dev)) { dev_err(dev, "Failed to register hwmon device\n"); return PTR_ERR(data->hwmon_dev); @@ -2625,7 +2642,7 @@ int pmbus_do_probe(struct i2c_client *client, struct pmbus_driver_info *info) return 0; } -EXPORT_SYMBOL_GPL(pmbus_do_probe); +EXPORT_SYMBOL_NS_GPL(pmbus_do_probe, PMBUS); struct dentry *pmbus_get_debugfs_dir(struct i2c_client *client) { @@ -2633,7 +2650,7 @@ struct dentry *pmbus_get_debugfs_dir(struct i2c_client *client) return data->debugfs; } -EXPORT_SYMBOL_GPL(pmbus_get_debugfs_dir); +EXPORT_SYMBOL_NS_GPL(pmbus_get_debugfs_dir, PMBUS); static int __init pmbus_core_init(void) { diff --git a/drivers/hwmon/pmbus/pxe1610.c b/drivers/hwmon/pmbus/pxe1610.c index da27ce34ee3f..52bee5de2988 100644 --- a/drivers/hwmon/pmbus/pxe1610.c +++ b/drivers/hwmon/pmbus/pxe1610.c @@ -41,6 +41,15 @@ static int pxe1610_identify(struct i2c_client *client, info->vrm_version[i] = vr13; break; default: + /* + * If prior pages are available limit operation + * to them + */ + if (i != 0) { + info->pages = i; + return 0; + } + return -ENODEV; } } @@ -139,3 +148,4 @@ module_i2c_driver(pxe1610_driver); MODULE_AUTHOR("Vijay Khemka <vijaykhemka@fb.com>"); MODULE_DESCRIPTION("PMBus driver for Infineon PXE1610, PXE1110 and PXM1310"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/q54sj108a2.c b/drivers/hwmon/pmbus/q54sj108a2.c index aec512766c31..b6e8b20466f1 100644 --- a/drivers/hwmon/pmbus/q54sj108a2.c +++ b/drivers/hwmon/pmbus/q54sj108a2.c @@ -420,3 +420,4 @@ module_i2c_driver(q54sj108a2_driver); MODULE_AUTHOR("Xiao.Ma <xiao.mx.ma@deltaww.com>"); MODULE_DESCRIPTION("PMBus driver for Delta Q54SJ108A2 series modules"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/stpddc60.c b/drivers/hwmon/pmbus/stpddc60.c new file mode 100644 index 000000000000..357b9d9d896b --- /dev/null +++ b/drivers/hwmon/pmbus/stpddc60.c @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for the STPDDC60 controller + * + * Copyright (c) 2021 Flextronics International Sweden AB. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/pmbus.h> +#include "pmbus.h" + +#define STPDDC60_MFR_READ_VOUT 0xd2 +#define STPDDC60_MFR_OV_LIMIT_OFFSET 0xe5 +#define STPDDC60_MFR_UV_LIMIT_OFFSET 0xe6 + +static const struct i2c_device_id stpddc60_id[] = { + {"stpddc60", 0}, + {"bmr481", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, stpddc60_id); + +static struct pmbus_driver_info stpddc60_info = { + .pages = 1, + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT + | PMBUS_HAVE_POUT, +}; + +/* + * Calculate the closest absolute offset between commanded vout value + * and limit value in steps of 50mv in the range 0 (50mv) to 7 (400mv). + * Return 0 if the upper limit is lower than vout or if the lower limit + * is higher than vout. + */ +static u8 stpddc60_get_offset(int vout, u16 limit, bool over) +{ + int offset; + long v, l; + + v = 250 + (vout - 1) * 5; /* Convert VID to mv */ + l = (limit * 1000L) >> 8; /* Convert LINEAR to mv */ + + if (over == (l < v)) + return 0; + + offset = DIV_ROUND_CLOSEST(abs(l - v), 50); + + if (offset > 0) + offset--; + + return clamp_val(offset, 0, 7); +} + +/* + * Adjust the linear format word to use the given fixed exponent. + */ +static u16 stpddc60_adjust_linear(u16 word, s16 fixed) +{ + s16 e, m, d; + + e = ((s16)word) >> 11; + m = ((s16)((word & 0x7ff) << 5)) >> 5; + d = e - fixed; + + if (d >= 0) + m <<= d; + else + m >>= -d; + + return clamp_val(m, 0, 0x3ff) | ((fixed << 11) & 0xf800); +} + +/* + * The VOUT_COMMAND register uses the VID format but the vout alarm limit + * registers use the LINEAR format so we override VOUT_MODE here to force + * LINEAR format for all registers. + */ +static int stpddc60_read_byte_data(struct i2c_client *client, int page, int reg) +{ + int ret; + + if (page > 0) + return -ENXIO; + + switch (reg) { + case PMBUS_VOUT_MODE: + ret = 0x18; + break; + default: + ret = -ENODATA; + break; + } + + return ret; +} + +/* + * The vout related registers return values in LINEAR11 format when LINEAR16 + * is expected. Clear the top 5 bits to set the exponent part to zero to + * convert the value to LINEAR16 format. + */ +static int stpddc60_read_word_data(struct i2c_client *client, int page, + int phase, int reg) +{ + int ret; + + if (page > 0) + return -ENXIO; + + switch (reg) { + case PMBUS_READ_VOUT: + ret = pmbus_read_word_data(client, page, phase, + STPDDC60_MFR_READ_VOUT); + if (ret < 0) + return ret; + ret &= 0x7ff; + break; + case PMBUS_VOUT_OV_FAULT_LIMIT: + case PMBUS_VOUT_UV_FAULT_LIMIT: + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + ret &= 0x7ff; + break; + default: + ret = -ENODATA; + break; + } + + return ret; +} + +/* + * The vout under- and over-voltage limits are set as an offset relative to + * the commanded vout voltage. The vin, iout, pout and temp limits must use + * the same fixed exponent the chip uses to encode the data when read. + */ +static int stpddc60_write_word_data(struct i2c_client *client, int page, + int reg, u16 word) +{ + int ret; + u8 offset; + + if (page > 0) + return -ENXIO; + + switch (reg) { + case PMBUS_VOUT_OV_FAULT_LIMIT: + ret = pmbus_read_word_data(client, page, 0xff, + PMBUS_VOUT_COMMAND); + if (ret < 0) + return ret; + offset = stpddc60_get_offset(ret, word, true); + ret = pmbus_write_byte_data(client, page, + STPDDC60_MFR_OV_LIMIT_OFFSET, + offset); + break; + case PMBUS_VOUT_UV_FAULT_LIMIT: + ret = pmbus_read_word_data(client, page, 0xff, + PMBUS_VOUT_COMMAND); + if (ret < 0) + return ret; + offset = stpddc60_get_offset(ret, word, false); + ret = pmbus_write_byte_data(client, page, + STPDDC60_MFR_UV_LIMIT_OFFSET, + offset); + break; + case PMBUS_VIN_OV_FAULT_LIMIT: + case PMBUS_VIN_UV_FAULT_LIMIT: + case PMBUS_OT_FAULT_LIMIT: + case PMBUS_OT_WARN_LIMIT: + case PMBUS_IOUT_OC_FAULT_LIMIT: + case PMBUS_IOUT_OC_WARN_LIMIT: + case PMBUS_POUT_OP_FAULT_LIMIT: + ret = pmbus_read_word_data(client, page, 0xff, reg); + if (ret < 0) + return ret; + word = stpddc60_adjust_linear(word, ret >> 11); + ret = pmbus_write_word_data(client, page, reg, word); + break; + default: + ret = -ENODATA; + break; + } + + return ret; +} + +static int stpddc60_probe(struct i2c_client *client) +{ + int status; + u8 device_id[I2C_SMBUS_BLOCK_MAX + 1]; + const struct i2c_device_id *mid; + struct pmbus_driver_info *info = &stpddc60_info; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_BYTE_DATA + | I2C_FUNC_SMBUS_BLOCK_DATA)) + return -ENODEV; + + status = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, device_id); + if (status < 0) { + dev_err(&client->dev, "Failed to read Manufacturer Model\n"); + return status; + } + for (mid = stpddc60_id; mid->name[0]; mid++) { + if (!strncasecmp(mid->name, device_id, strlen(mid->name))) + break; + } + if (!mid->name[0]) { + dev_err(&client->dev, "Unsupported device\n"); + return -ENODEV; + } + + info->read_byte_data = stpddc60_read_byte_data; + info->read_word_data = stpddc60_read_word_data; + info->write_word_data = stpddc60_write_word_data; + + status = pmbus_do_probe(client, info); + if (status < 0) + return status; + + pmbus_set_update(client, PMBUS_VOUT_OV_FAULT_LIMIT, true); + pmbus_set_update(client, PMBUS_VOUT_UV_FAULT_LIMIT, true); + + return 0; +} + +static struct i2c_driver stpddc60_driver = { + .driver = { + .name = "stpddc60", + }, + .probe_new = stpddc60_probe, + .id_table = stpddc60_id, +}; + +module_i2c_driver(stpddc60_driver); + +MODULE_AUTHOR("Erik Rosen <erik.rosen@metormote.com>"); +MODULE_DESCRIPTION("PMBus driver for ST STPDDC60"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/tps40422.c b/drivers/hwmon/pmbus/tps40422.c index f7f00ab6f46c..31bb83c0ef3e 100644 --- a/drivers/hwmon/pmbus/tps40422.c +++ b/drivers/hwmon/pmbus/tps40422.c @@ -51,3 +51,4 @@ module_i2c_driver(tps40422_driver); MODULE_AUTHOR("Zhu Laiwen <richard.zhu@nsn.com>"); MODULE_DESCRIPTION("PMBus driver for TI TPS40422"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/tps53679.c b/drivers/hwmon/pmbus/tps53679.c index ba838fa311c3..81b9d813655a 100644 --- a/drivers/hwmon/pmbus/tps53679.c +++ b/drivers/hwmon/pmbus/tps53679.c @@ -16,11 +16,14 @@ #include "pmbus.h" enum chips { - tps53647, tps53667, tps53679, tps53681, tps53688 + tps53647, tps53667, tps53676, tps53679, tps53681, tps53688 }; #define TPS53647_PAGE_NUM 1 +#define TPS53676_USER_DATA_03 0xb3 +#define TPS53676_MAX_PHASES 7 + #define TPS53679_PROT_VR12_5MV 0x01 /* VR12.0 mode, 5-mV DAC */ #define TPS53679_PROT_VR12_5_10MV 0x02 /* VR12.5 mode, 10-mV DAC */ #define TPS53679_PROT_VR13_10MV 0x04 /* VR13.0 mode, 10-mV DAC */ @@ -143,6 +146,45 @@ static int tps53681_identify(struct i2c_client *client, TPS53681_DEVICE_ID); } +static int tps53676_identify(struct i2c_client *client, + struct pmbus_driver_info *info) +{ + u8 buf[I2C_SMBUS_BLOCK_MAX]; + int phases_a = 0, phases_b = 0; + int i, ret; + + ret = i2c_smbus_read_block_data(client, PMBUS_IC_DEVICE_ID, buf); + if (ret < 0) + return ret; + if (strncmp("TI\x53\x67\x60", buf, 5)) { + dev_err(&client->dev, "Unexpected device ID: %s\n", buf); + return -ENODEV; + } + + ret = i2c_smbus_read_block_data(client, TPS53676_USER_DATA_03, buf); + if (ret < 0) + return ret; + if (ret != 24) + return -EIO; + for (i = 0; i < 2 * TPS53676_MAX_PHASES; i += 2) { + if (buf[i + 1] & 0x80) { + if (buf[i] & 0x08) + phases_b++; + else + phases_a++; + } + } + + info->format[PSC_VOLTAGE_OUT] = linear; + info->pages = 1; + info->phases[0] = phases_a; + if (phases_b > 0) { + info->pages = 2; + info->phases[1] = phases_b; + } + return 0; +} + static int tps53681_read_word_data(struct i2c_client *client, int page, int phase, int reg) { @@ -183,6 +225,7 @@ static struct pmbus_driver_info tps53679_info = { .pfunc[3] = PMBUS_HAVE_IOUT, .pfunc[4] = PMBUS_HAVE_IOUT, .pfunc[5] = PMBUS_HAVE_IOUT, + .pfunc[6] = PMBUS_HAVE_IOUT, }; static int tps53679_probe(struct i2c_client *client) @@ -206,6 +249,9 @@ static int tps53679_probe(struct i2c_client *client) info->pages = TPS53647_PAGE_NUM; info->identify = tps53679_identify; break; + case tps53676: + info->identify = tps53676_identify; + break; case tps53679: case tps53688: info->pages = TPS53679_PAGE_NUM; @@ -225,8 +271,10 @@ static int tps53679_probe(struct i2c_client *client) } static const struct i2c_device_id tps53679_id[] = { + {"bmr474", tps53676}, {"tps53647", tps53647}, {"tps53667", tps53667}, + {"tps53676", tps53676}, {"tps53679", tps53679}, {"tps53681", tps53681}, {"tps53688", tps53688}, @@ -238,6 +286,7 @@ MODULE_DEVICE_TABLE(i2c, tps53679_id); static const struct of_device_id __maybe_unused tps53679_of_match[] = { {.compatible = "ti,tps53647", .data = (void *)tps53647}, {.compatible = "ti,tps53667", .data = (void *)tps53667}, + {.compatible = "ti,tps53676", .data = (void *)tps53676}, {.compatible = "ti,tps53679", .data = (void *)tps53679}, {.compatible = "ti,tps53681", .data = (void *)tps53681}, {.compatible = "ti,tps53688", .data = (void *)tps53688}, @@ -259,3 +308,4 @@ module_i2c_driver(tps53679_driver); MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>"); MODULE_DESCRIPTION("PMBus driver for Texas Instruments TPS53679"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/ucd9000.c b/drivers/hwmon/pmbus/ucd9000.c index a15e6fe3e425..75fc770c9e40 100644 --- a/drivers/hwmon/pmbus/ucd9000.c +++ b/drivers/hwmon/pmbus/ucd9000.c @@ -629,3 +629,4 @@ module_i2c_driver(ucd9000_driver); MODULE_AUTHOR("Guenter Roeck"); MODULE_DESCRIPTION("PMBus driver for TI UCD90xxx"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/ucd9200.c b/drivers/hwmon/pmbus/ucd9200.c index 47cc7ca9d329..6bc3273e31e7 100644 --- a/drivers/hwmon/pmbus/ucd9200.c +++ b/drivers/hwmon/pmbus/ucd9200.c @@ -209,3 +209,4 @@ module_i2c_driver(ucd9200_driver); MODULE_AUTHOR("Guenter Roeck"); MODULE_DESCRIPTION("PMBus driver for TI UCD922x, UCD924x"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/xdpe12284.c b/drivers/hwmon/pmbus/xdpe12284.c index f8bc0f41cd5f..b07da06a40c9 100644 --- a/drivers/hwmon/pmbus/xdpe12284.c +++ b/drivers/hwmon/pmbus/xdpe12284.c @@ -168,3 +168,4 @@ module_i2c_driver(xdpe122_driver); MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>"); MODULE_DESCRIPTION("PMBus driver for Infineon XDPE122 family"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/zl6100.c b/drivers/hwmon/pmbus/zl6100.c index 69120ca7aaa8..b7d4eacdc3ef 100644 --- a/drivers/hwmon/pmbus/zl6100.c +++ b/drivers/hwmon/pmbus/zl6100.c @@ -404,3 +404,4 @@ module_i2c_driver(zl6100_driver); MODULE_AUTHOR("Guenter Roeck"); MODULE_DESCRIPTION("PMBus driver for ZL6100 and compatibles"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); |