diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
commit | 2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch) | |
tree | 848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/hwmon/pmbus | |
parent | Initial commit. (diff) | |
download | linux-upstream.tar.xz linux-upstream.zip |
Adding upstream version 6.1.76.upstream/6.1.76upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/hwmon/pmbus')
48 files changed, 17831 insertions, 0 deletions
diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig new file mode 100644 index 000000000..89668af67 --- /dev/null +++ b/drivers/hwmon/pmbus/Kconfig @@ -0,0 +1,464 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# PMBus chip drivers configuration +# + +menuconfig PMBUS + tristate "PMBus support" + depends on I2C + help + Say yes here if you want to enable PMBus support. + + This driver can also be built as a module. If so, the module will + be called pmbus_core. + +if PMBUS + +config SENSORS_PMBUS + tristate "Generic PMBus devices" + default y + help + If you say yes here you get hardware monitoring support for generic + PMBus devices, including but not limited to ADP4000, BMR310, BMR453, + BMR454, BMR456, BMR457, BMR458, BMR480, BMR490, BMR491, BMR492, + MAX20796, MDT040, NCP4200, NCP4208, PDT003, PDT006, PDT012, + TPS40400, TPS544B20, TPS544B25, TPS544C20, TPS544C25, and UDT020. + + This driver can also be built as a module. If so, the module will + be called pmbus. + +config SENSORS_ADM1266 + tristate "Analog Devices ADM1266 Sequencer" + select CRC8 + depends on GPIOLIB + help + If you say yes here you get hardware monitoring support for Analog + Devices ADM1266 Cascadable Super Sequencer. + + This driver can also be built as a module. If so, the module will + be called adm1266. + +config SENSORS_ADM1275 + tristate "Analog Devices ADM1275 and compatibles" + help + If you say yes here you get hardware monitoring support for Analog + Devices ADM1075, ADM1272, ADM1275, ADM1276, ADM1278, ADM1293, + and ADM1294 Hot-Swap Controller and Digital Power Monitors. + + This driver can also be built as a module. If so, the module will + be called adm1275. + +config SENSORS_BEL_PFE + tristate "Bel PFE Compatible Power Supplies" + help + If you say yes here you get hardware monitoring support for BEL + PFE1100 and PFE3000 Power Supplies. + + 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_DELTA_AHE50DC_FAN + tristate "Delta AHE-50DC fan control module" + help + If you say yes here you get hardware monitoring support for + the integrated fan control module of the Delta AHE-50DC + Open19 power shelf. + + This driver can also be built as a module. If so, the module + will be called delta-ahe50dc-fan. + +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 + help + If you say yes here you get hardware monitoring support for the IBM + Common Form Factor power supply. + + This driver can also be built as a module. If so, the module will + be called ibm-cffps. + +config SENSORS_DPS920AB + tristate "Delta DPS920AB Power Supply" + help + If you say yes here you get hardware monitoring support for Delta + DPS920AB Power Supplies. + + This driver can also be built as a module. If so, the module will + be called dps920ab. + +config SENSORS_INSPUR_IPSPS + tristate "INSPUR Power System Power Supply" + help + If you say yes here you get hardware monitoring support for the INSPUR + Power System power supply. + + This driver can also be built as a module. If so, the module will + be called inspur-ipsps. + +config SENSORS_IR35221 + tristate "Infineon IR35221" + help + If you say yes here you get hardware monitoring support for the + Infineon IR35221 controller. + + 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 and compatibles" + help + If you say yes here you get hardware monitoring support for Infineon + IR38060, IR38064, IR38164 and IR38263. + + This driver can also be built as a module. If so, the module will + be called ir38064. + +config SENSORS_IR38064_REGULATOR + bool "Regulator support for IR38064 and compatibles" + depends on SENSORS_IR38064 && REGULATOR + help + Uses the IR38064 or compatible as regulator. + +config SENSORS_IRPS5401 + tristate "Infineon IRPS5401" + help + If you say yes here you get hardware monitoring support for the + Infineon IRPS5401 controller. + + This driver can also be built as a module. If so, the module will + be called irps5401. + +config SENSORS_ISL68137 + tristate "Renesas Digital Multiphase Voltage Regulators" + help + If you say yes here you get hardware monitoring support for Renesas + digital multiphase voltage regulators. + + This driver can also be built as a module. If so, the module will + be called isl68137. + +config SENSORS_LM25066 + tristate "National Semiconductor LM25066 and compatibles" + help + If you say yes here you get hardware monitoring support for National + Semiconductor LM25056, LM25066, LM5064, and LM5066. + + This driver can also be built as a module. If so, the module will + be called lm25066. + +config SENSORS_LM25066_REGULATOR + bool "Regulator support for LM25066 and compatibles" + depends on SENSORS_LM25066 && REGULATOR + help + If you say yes here you get regulator support for National + Semiconductor LM25066, LM5064, and LM5066. + +config SENSORS_LT7182S + tristate "Analog Devices LT7182S" + help + If you say yes here you get hardware monitoring support for Analog + Devices LT7182S. + + This driver can also be built as a module. If so, the module will + be called lt7182s. + +config SENSORS_LTC2978 + tristate "Linear Technologies LTC2978 and compatibles" + help + If you say yes here you get hardware monitoring support for Linear + Technology LTC2972, LTC2974, LTC2975, LTC2977, LTC2978, LTC2979, + LTC2980, and LTM2987. + + This driver can also be built as a module. If so, the module will + be called ltc2978. + +config SENSORS_LTC2978_REGULATOR + bool "Regulator support for LTC2978 and compatibles" + depends on SENSORS_LTC2978 && REGULATOR + help + If you say yes here you get regulator support for Linear Technology + LTC3880, LTC3883, LTC3884, LTC3886, LTC3887, LTC3889, LTC7880, + LTM4644, LTM4675, LTM4676, LTM4677, LTM4678, LTM4680, LTM4686, + and LTM4700. + +config SENSORS_LTC3815 + tristate "Linear Technologies LTC3815" + help + If you say yes here you get hardware monitoring support for Linear + Technology 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 + If you say yes here you get hardware monitoring support for Maxim + MAX16064. + + This driver can also be built as a module. If so, the module will + be called max16064. + +config SENSORS_MAX16601 + tristate "Maxim MAX16508, MAX16601, MAX16602" + help + If you say yes here you get hardware monitoring support for Maxim + MAX16508, MAX16601 and MAX16602. + + This driver can also be built as a module. If so, the module will + be called max16601. + +config SENSORS_MAX20730 + tristate "Maxim MAX20710, MAX20730, MAX20734, MAX20743" + help + If you say yes here you get hardware monitoring support for Maxim + MAX20710, MAX20730, MAX20734, and MAX20743. + + This driver can also be built as a module. If so, the module will + be called max20730. + +config SENSORS_MAX20751 + tristate "Maxim MAX20751" + help + If you say yes here you get hardware monitoring support for Maxim + MAX20751. + + This driver can also be built as a module. If so, the module will + be called max20751. + +config SENSORS_MAX31785 + tristate "Maxim MAX31785 and compatibles" + help + If you say yes here you get hardware monitoring support for Maxim + MAX31785. + + This driver can also be built as a module. If so, the module will + be called max31785. + +config SENSORS_MAX34440 + tristate "Maxim MAX34440 and compatibles" + help + If you say yes here you get hardware monitoring support for Maxim + MAX34440, MAX34441, MAX34446, MAX34451, MAX34460, and MAX34461. + + This driver can also be built as a module. If so, the module will + be called max34440. + +config SENSORS_MAX8688 + tristate "Maxim MAX8688" + help + If you say yes here you get hardware monitoring support for Maxim + MAX8688. + + This driver can also be built as a module. If so, the module will + be called max8688. + +config SENSORS_MP2888 + tristate "MPS MP2888" + help + If you say yes here you get hardware monitoring support for MPS + MP2888 Digital, Multi-Phase, Pulse-Width Modulation Controller. + + This driver can also be built as a module. If so, the module will + be called mp2888. + +config SENSORS_MP2975 + tristate "MPS MP2975" + help + If you say yes here you get hardware monitoring support for MPS + MP2975 Dual Loop Digital Multi-Phase Controller. + + This driver can also be built as a module. If so, the module will + be called mp2975. + +config SENSORS_MP5023 + tristate "MPS MP5023" + help + If you say yes here you get hardware monitoring support for MPS + MP5023. + + This driver can also be built as a module. If so, the module will + be called mp5023. + +config SENSORS_PIM4328 + tristate "Flex PIM4328 and compatibles" + help + If you say yes here you get hardware monitoring support for Flex + PIM4328, PIM4820 and PIM4006 Power Interface Modules. + + This driver can also be built as a module. If so, the module will + be called pim4328. + +config SENSORS_PLI1209BC + tristate "Vicor PLI1209BC" + help + If you say yes here you get hardware monitoring support for Vicor + PLI1209BC Digital Supervisor. + + This driver can also be built as a module. If so, the module will + be called pli1209bc. + +config SENSORS_PLI1209BC_REGULATOR + bool "Regulator support for PLI1209BC" + depends on SENSORS_PLI1209BC && REGULATOR + help + If you say yes here you get regulator support for Vicor PLI1209BC + Digital Supervisor. + +config SENSORS_PM6764TR + tristate "ST PM6764TR" + help + If you say yes here you get hardware monitoring support for ST + PM6764TR. + + This driver can also be built as a module. If so, the module will + be called pm6764tr. + +config SENSORS_PXE1610 + tristate "Infineon PXE1610" + help + If you say yes here you get hardware monitoring support for Infineon + PXE1610. + + This driver can also be built as a module. If so, the module will + be called pxe1610. + +config SENSORS_Q54SJ108A2 + tristate "Delta Power Supplies Q54SJ108A2" + help + If you say yes here you get hardware monitoring support for Delta + Q54SJ108A2 series Power Supplies. + + 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 + If you say yes here you get hardware monitoring support for TI + TPS40422. + + This driver can also be built as a module. If so, the module will + be called tps40422. + +config SENSORS_TPS53679 + tristate "TI TPS53647, TPS53667, TPS53676, TPS53679, TPS53681, TPS53688" + help + If you say yes here you get hardware monitoring support for TI + TPS53647, TPS53667, TPS53676, TPS53679, TPS53681, and TPS53688. + + This driver can also be built as a module. If so, the module will + be called tps53679. + +config SENSORS_TPS546D24 + tristate "TPS546D24" + help + If you say yes here you get hardware monitoring support for TEXAS + TPS546D24. + + This driver can also be built as a module. If so, the module will + be called tps546d24 + +config SENSORS_UCD9000 + tristate "TI UCD90120, UCD90124, UCD90160, UCD90320, UCD9090, UCD90910" + help + If you say yes here you get hardware monitoring support for TI + UCD90120, UCD90124, UCD90160, UCD90320, UCD9090, UCD90910, Sequencer + and System Health Controllers. + + This driver can also be built as a module. If so, the module will + be called ucd9000. + +config SENSORS_UCD9200 + tristate "TI UCD9220, UCD9222, UCD9224, UCD9240, UCD9244, UCD9246, UCD9248" + help + If you say yes here you get hardware monitoring support for TI + UCD9220, UCD9222, UCD9224, UCD9240, UCD9244, UCD9246, and UCD9248 + Digital PWM System Controllers. + + This driver can also be built as a module. If so, the module will + be called ucd9200. + +config SENSORS_XDPE152 + tristate "Infineon XDPE152 family" + help + If you say yes here you get hardware monitoring support for Infineon + XDPE15284, XDPE152C4, device. + + This driver can also be built as a module. If so, the module will + be called xdpe152c4. + +config SENSORS_XDPE122 + tristate "Infineon XDPE122 family" + help + If you say yes here you get hardware monitoring support for Infineon + XDPE12254, XDPE12284, device. + + This driver can also be built as a module. If so, the module will + be called xdpe12284. + +config SENSORS_XDPE122_REGULATOR + bool "Regulator support for XDPE122 and compatibles" + depends on SENSORS_XDPE122 && REGULATOR + help + Uses the xdpe12284 or compatible as regulator. + +config SENSORS_ZL6100 + tristate "Intersil ZL6100 and compatibles" + help + If you say yes here you get hardware monitoring support for Intersil + ZL2004, ZL2005, ZL2006, ZL2008, ZL2105, ZL2106, ZL6100, ZL6105, + ZL9101M, and ZL9117M Digital DC/DC Controllers, as well as for + Ericsson BMR450, BMR451, BMR462, BMR463, and BMR464. + + This driver can also be built as a module. If so, the module will + be called zl6100. + +endif # PMBUS diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile new file mode 100644 index 000000000..0002dbe22 --- /dev/null +++ b/drivers/hwmon/pmbus/Makefile @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for PMBus chip drivers. +# + +obj-$(CONFIG_PMBUS) += pmbus_core.o +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_DELTA_AHE50DC_FAN) += delta-ahe50dc-fan.o +obj-$(CONFIG_SENSORS_FSP_3Y) += fsp-3y.o +obj-$(CONFIG_SENSORS_IBM_CFFPS) += ibm-cffps.o +obj-$(CONFIG_SENSORS_DPS920AB) += dps920ab.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_LT7182S) += lt7182s.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 +obj-$(CONFIG_SENSORS_MAX20751) += max20751.o +obj-$(CONFIG_SENSORS_MAX31785) += max31785.o +obj-$(CONFIG_SENSORS_MAX34440) += max34440.o +obj-$(CONFIG_SENSORS_MAX8688) += max8688.o +obj-$(CONFIG_SENSORS_MP2888) += mp2888.o +obj-$(CONFIG_SENSORS_MP2975) += mp2975.o +obj-$(CONFIG_SENSORS_MP5023) += mp5023.o +obj-$(CONFIG_SENSORS_PLI1209BC) += pli1209bc.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_TPS546D24) += tps546d24.o +obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o +obj-$(CONFIG_SENSORS_UCD9200) += ucd9200.o +obj-$(CONFIG_SENSORS_XDPE122) += xdpe12284.o +obj-$(CONFIG_SENSORS_XDPE152) += xdpe152c4.o +obj-$(CONFIG_SENSORS_ZL6100) += zl6100.o +obj-$(CONFIG_SENSORS_PIM4328) += pim4328.o diff --git a/drivers/hwmon/pmbus/adm1266.c b/drivers/hwmon/pmbus/adm1266.c new file mode 100644 index 000000000..1ac2b2f4c --- /dev/null +++ b/drivers/hwmon/pmbus/adm1266.c @@ -0,0 +1,514 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ADM1266 - Cascadable Super Sequencer with Margin + * Control and Fault Recording + * + * Copyright 2020 Analog Devices Inc. + */ + +#include <linux/bitfield.h> +#include <linux/crc8.h> +#include <linux/debugfs.h> +#include <linux/gpio/driver.h> +#include <linux/i2c.h> +#include <linux/i2c-smbus.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/nvmem-consumer.h> +#include <linux/nvmem-provider.h> +#include "pmbus.h" +#include <linux/slab.h> +#include <linux/timekeeping.h> + +#define ADM1266_BLACKBOX_CONFIG 0xD3 +#define ADM1266_PDIO_CONFIG 0xD4 +#define ADM1266_READ_STATE 0xD9 +#define ADM1266_READ_BLACKBOX 0xDE +#define ADM1266_SET_RTC 0xDF +#define ADM1266_GPIO_CONFIG 0xE1 +#define ADM1266_BLACKBOX_INFO 0xE6 +#define ADM1266_PDIO_STATUS 0xE9 +#define ADM1266_GPIO_STATUS 0xEA + +/* ADM1266 GPIO defines */ +#define ADM1266_GPIO_NR 9 +#define ADM1266_GPIO_FUNCTIONS(x) FIELD_GET(BIT(0), x) +#define ADM1266_GPIO_INPUT_EN(x) FIELD_GET(BIT(2), x) +#define ADM1266_GPIO_OUTPUT_EN(x) FIELD_GET(BIT(3), x) +#define ADM1266_GPIO_OPEN_DRAIN(x) FIELD_GET(BIT(4), x) + +/* ADM1266 PDIO defines */ +#define ADM1266_PDIO_NR 16 +#define ADM1266_PDIO_PIN_CFG(x) FIELD_GET(GENMASK(15, 13), x) +#define ADM1266_PDIO_GLITCH_FILT(x) FIELD_GET(GENMASK(12, 9), x) +#define ADM1266_PDIO_OUT_CFG(x) FIELD_GET(GENMASK(2, 0), x) + +#define ADM1266_BLACKBOX_OFFSET 0 +#define ADM1266_BLACKBOX_SIZE 64 + +#define ADM1266_PMBUS_BLOCK_MAX 255 + +struct adm1266_data { + struct pmbus_driver_info info; + struct gpio_chip gc; + const char *gpio_names[ADM1266_GPIO_NR + ADM1266_PDIO_NR]; + struct i2c_client *client; + struct dentry *debugfs_dir; + struct nvmem_config nvmem_config; + struct nvmem_device *nvmem; + u8 *dev_mem; + struct mutex buf_mutex; + u8 write_buf[ADM1266_PMBUS_BLOCK_MAX + 1] ____cacheline_aligned; + u8 read_buf[ADM1266_PMBUS_BLOCK_MAX + 1] ____cacheline_aligned; +}; + +static const struct nvmem_cell_info adm1266_nvmem_cells[] = { + { + .name = "blackbox", + .offset = ADM1266_BLACKBOX_OFFSET, + .bytes = 2048, + }, +}; + +DECLARE_CRC8_TABLE(pmbus_crc_table); + +/* + * Different from Block Read as it sends data and waits for the slave to + * return a value dependent on that data. The protocol is simply a Write Block + * followed by a Read Block without the Read-Block command field and the + * Write-Block STOP bit. + */ +static int adm1266_pmbus_block_xfer(struct adm1266_data *data, u8 cmd, u8 w_len, u8 *data_w, + u8 *data_r) +{ + struct i2c_client *client = data->client; + struct i2c_msg msgs[2] = { + { + .addr = client->addr, + .flags = I2C_M_DMA_SAFE, + .buf = data->write_buf, + .len = w_len + 2, + }, + { + .addr = client->addr, + .flags = I2C_M_RD | I2C_M_DMA_SAFE, + .buf = data->read_buf, + .len = ADM1266_PMBUS_BLOCK_MAX + 2, + } + }; + u8 addr; + u8 crc; + int ret; + + mutex_lock(&data->buf_mutex); + + msgs[0].buf[0] = cmd; + msgs[0].buf[1] = w_len; + memcpy(&msgs[0].buf[2], data_w, w_len); + + ret = i2c_transfer(client->adapter, msgs, 2); + if (ret != 2) { + if (ret >= 0) + ret = -EPROTO; + + mutex_unlock(&data->buf_mutex); + + return ret; + } + + if (client->flags & I2C_CLIENT_PEC) { + addr = i2c_8bit_addr_from_msg(&msgs[0]); + crc = crc8(pmbus_crc_table, &addr, 1, 0); + crc = crc8(pmbus_crc_table, msgs[0].buf, msgs[0].len, crc); + + addr = i2c_8bit_addr_from_msg(&msgs[1]); + crc = crc8(pmbus_crc_table, &addr, 1, crc); + crc = crc8(pmbus_crc_table, msgs[1].buf, msgs[1].buf[0] + 1, crc); + + if (crc != msgs[1].buf[msgs[1].buf[0] + 1]) { + mutex_unlock(&data->buf_mutex); + return -EBADMSG; + } + } + + memcpy(data_r, &msgs[1].buf[1], msgs[1].buf[0]); + + ret = msgs[1].buf[0]; + mutex_unlock(&data->buf_mutex); + + return ret; +} + +static const unsigned int adm1266_gpio_mapping[ADM1266_GPIO_NR][2] = { + {1, 0}, + {2, 1}, + {3, 2}, + {4, 8}, + {5, 9}, + {6, 10}, + {7, 11}, + {8, 6}, + {9, 7}, +}; + +static const char *adm1266_names[ADM1266_GPIO_NR + ADM1266_PDIO_NR] = { + "GPIO1", "GPIO2", "GPIO3", "GPIO4", "GPIO5", "GPIO6", "GPIO7", "GPIO8", + "GPIO9", "PDIO1", "PDIO2", "PDIO3", "PDIO4", "PDIO5", "PDIO6", + "PDIO7", "PDIO8", "PDIO9", "PDIO10", "PDIO11", "PDIO12", "PDIO13", + "PDIO14", "PDIO15", "PDIO16", +}; + +static int adm1266_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + struct adm1266_data *data = gpiochip_get_data(chip); + u8 read_buf[I2C_SMBUS_BLOCK_MAX + 1]; + unsigned long pins_status; + unsigned int pmbus_cmd; + int ret; + + if (offset < ADM1266_GPIO_NR) + pmbus_cmd = ADM1266_GPIO_STATUS; + else + pmbus_cmd = ADM1266_PDIO_STATUS; + + ret = i2c_smbus_read_block_data(data->client, pmbus_cmd, read_buf); + if (ret < 0) + return ret; + + pins_status = read_buf[0] + (read_buf[1] << 8); + if (offset < ADM1266_GPIO_NR) + return test_bit(adm1266_gpio_mapping[offset][1], &pins_status); + + return test_bit(offset - ADM1266_GPIO_NR, &pins_status); +} + +static int adm1266_gpio_get_multiple(struct gpio_chip *chip, unsigned long *mask, + unsigned long *bits) +{ + struct adm1266_data *data = gpiochip_get_data(chip); + u8 read_buf[ADM1266_PMBUS_BLOCK_MAX + 1]; + unsigned long status; + unsigned int gpio_nr; + int ret; + + ret = i2c_smbus_read_block_data(data->client, ADM1266_GPIO_STATUS, read_buf); + if (ret < 0) + return ret; + + status = read_buf[0] + (read_buf[1] << 8); + + *bits = 0; + for_each_set_bit(gpio_nr, mask, ADM1266_GPIO_NR) { + if (test_bit(adm1266_gpio_mapping[gpio_nr][1], &status)) + set_bit(gpio_nr, bits); + } + + ret = i2c_smbus_read_block_data(data->client, ADM1266_PDIO_STATUS, read_buf); + if (ret < 0) + return ret; + + status = read_buf[0] + (read_buf[1] << 8); + + *bits = 0; + for_each_set_bit_from(gpio_nr, mask, ADM1266_GPIO_NR + ADM1266_PDIO_STATUS) { + if (test_bit(gpio_nr - ADM1266_GPIO_NR, &status)) + set_bit(gpio_nr, bits); + } + + return 0; +} + +static void adm1266_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) +{ + struct adm1266_data *data = gpiochip_get_data(chip); + u8 read_buf[ADM1266_PMBUS_BLOCK_MAX + 1]; + unsigned long gpio_config; + unsigned long pdio_config; + unsigned long pin_cfg; + u8 write_cmd; + int ret; + int i; + + for (i = 0; i < ADM1266_GPIO_NR; i++) { + write_cmd = adm1266_gpio_mapping[i][1]; + ret = adm1266_pmbus_block_xfer(data, ADM1266_GPIO_CONFIG, 1, &write_cmd, read_buf); + if (ret != 2) + return; + + gpio_config = read_buf[0]; + seq_puts(s, adm1266_names[i]); + + seq_puts(s, " ( "); + if (!ADM1266_GPIO_FUNCTIONS(gpio_config)) { + seq_puts(s, "high-Z )\n"); + continue; + } + if (ADM1266_GPIO_INPUT_EN(gpio_config)) + seq_puts(s, "input "); + if (ADM1266_GPIO_OUTPUT_EN(gpio_config)) + seq_puts(s, "output "); + if (ADM1266_GPIO_OPEN_DRAIN(gpio_config)) + seq_puts(s, "open-drain )\n"); + else + seq_puts(s, "push-pull )\n"); + } + + write_cmd = 0xFF; + ret = adm1266_pmbus_block_xfer(data, ADM1266_PDIO_CONFIG, 1, &write_cmd, read_buf); + if (ret != 32) + return; + + for (i = 0; i < ADM1266_PDIO_NR; i++) { + seq_puts(s, adm1266_names[ADM1266_GPIO_NR + i]); + + pdio_config = read_buf[2 * i]; + pdio_config += (read_buf[2 * i + 1] << 8); + pin_cfg = ADM1266_PDIO_PIN_CFG(pdio_config); + + seq_puts(s, " ( "); + if (!pin_cfg || pin_cfg > 5) { + seq_puts(s, "high-Z )\n"); + continue; + } + + if (pin_cfg & BIT(0)) + seq_puts(s, "output "); + + if (pin_cfg & BIT(1)) + seq_puts(s, "input "); + + seq_puts(s, ")\n"); + } +} + +static int adm1266_config_gpio(struct adm1266_data *data) +{ + const char *name = dev_name(&data->client->dev); + char *gpio_name; + int ret; + int i; + + for (i = 0; i < ARRAY_SIZE(data->gpio_names); i++) { + gpio_name = devm_kasprintf(&data->client->dev, GFP_KERNEL, "adm1266-%x-%s", + data->client->addr, adm1266_names[i]); + if (!gpio_name) + return -ENOMEM; + + data->gpio_names[i] = gpio_name; + } + + data->gc.label = name; + data->gc.parent = &data->client->dev; + data->gc.owner = THIS_MODULE; + data->gc.can_sleep = true; + data->gc.base = -1; + data->gc.names = data->gpio_names; + data->gc.ngpio = ARRAY_SIZE(data->gpio_names); + data->gc.get = adm1266_gpio_get; + data->gc.get_multiple = adm1266_gpio_get_multiple; + data->gc.dbg_show = adm1266_gpio_dbg_show; + + ret = devm_gpiochip_add_data(&data->client->dev, &data->gc, data); + if (ret) + dev_err(&data->client->dev, "GPIO registering failed (%d)\n", ret); + + return ret; +} + +static int adm1266_state_read(struct seq_file *s, void *pdata) +{ + struct device *dev = s->private; + struct i2c_client *client = to_i2c_client(dev); + int ret; + + ret = i2c_smbus_read_word_data(client, ADM1266_READ_STATE); + if (ret < 0) + return ret; + + seq_printf(s, "%d\n", ret); + + return 0; +} + +static void adm1266_init_debugfs(struct adm1266_data *data) +{ + struct dentry *root; + + root = pmbus_get_debugfs_dir(data->client); + if (!root) + return; + + data->debugfs_dir = debugfs_create_dir(data->client->name, root); + if (!data->debugfs_dir) + return; + + debugfs_create_devm_seqfile(&data->client->dev, "sequencer_state", data->debugfs_dir, + adm1266_state_read); +} + +static int adm1266_nvmem_read_blackbox(struct adm1266_data *data, u8 *read_buff) +{ + int record_count; + char index; + u8 buf[5]; + int ret; + + ret = i2c_smbus_read_block_data(data->client, ADM1266_BLACKBOX_INFO, buf); + if (ret < 0) + return ret; + + if (ret != 4) + return -EIO; + + record_count = buf[3]; + + for (index = 0; index < record_count; index++) { + ret = adm1266_pmbus_block_xfer(data, ADM1266_READ_BLACKBOX, 1, &index, read_buff); + if (ret < 0) + return ret; + + if (ret != ADM1266_BLACKBOX_SIZE) + return -EIO; + + read_buff += ADM1266_BLACKBOX_SIZE; + } + + return 0; +} + +static int adm1266_nvmem_read(void *priv, unsigned int offset, void *val, size_t bytes) +{ + struct adm1266_data *data = priv; + int ret; + + if (offset + bytes > data->nvmem_config.size) + return -EINVAL; + + if (offset == 0) { + memset(data->dev_mem, 0, data->nvmem_config.size); + + ret = adm1266_nvmem_read_blackbox(data, data->dev_mem); + if (ret) { + dev_err(&data->client->dev, "Could not read blackbox!"); + return ret; + } + } + + memcpy(val, data->dev_mem + offset, bytes); + + return 0; +} + +static int adm1266_config_nvmem(struct adm1266_data *data) +{ + data->nvmem_config.name = dev_name(&data->client->dev); + data->nvmem_config.dev = &data->client->dev; + data->nvmem_config.root_only = true; + data->nvmem_config.read_only = true; + data->nvmem_config.owner = THIS_MODULE; + data->nvmem_config.reg_read = adm1266_nvmem_read; + data->nvmem_config.cells = adm1266_nvmem_cells; + data->nvmem_config.ncells = ARRAY_SIZE(adm1266_nvmem_cells); + data->nvmem_config.priv = data; + data->nvmem_config.stride = 1; + data->nvmem_config.word_size = 1; + data->nvmem_config.size = adm1266_nvmem_cells[0].bytes; + + data->dev_mem = devm_kzalloc(&data->client->dev, data->nvmem_config.size, GFP_KERNEL); + if (!data->dev_mem) + return -ENOMEM; + + data->nvmem = devm_nvmem_register(&data->client->dev, &data->nvmem_config); + if (IS_ERR(data->nvmem)) { + dev_err(&data->client->dev, "Could not register nvmem!"); + return PTR_ERR(data->nvmem); + } + + return 0; +} + +static int adm1266_set_rtc(struct adm1266_data *data) +{ + time64_t kt; + char write_buf[6]; + int i; + + kt = ktime_get_seconds(); + + memset(write_buf, 0, sizeof(write_buf)); + + for (i = 0; i < 4; i++) + write_buf[2 + i] = (kt >> (i * 8)) & 0xFF; + + return i2c_smbus_write_block_data(data->client, ADM1266_SET_RTC, sizeof(write_buf), + write_buf); +} + +static int adm1266_probe(struct i2c_client *client) +{ + struct adm1266_data *data; + int ret; + int i; + + data = devm_kzalloc(&client->dev, sizeof(struct adm1266_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + data->info.pages = 17; + data->info.format[PSC_VOLTAGE_OUT] = linear; + for (i = 0; i < data->info.pages; i++) + data->info.func[i] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; + + crc8_populate_msb(pmbus_crc_table, 0x7); + mutex_init(&data->buf_mutex); + + ret = adm1266_config_gpio(data); + if (ret < 0) + return ret; + + ret = adm1266_set_rtc(data); + if (ret < 0) + return ret; + + ret = adm1266_config_nvmem(data); + if (ret < 0) + return ret; + + ret = pmbus_do_probe(client, &data->info); + if (ret) + return ret; + + adm1266_init_debugfs(data); + + return 0; +} + +static const struct of_device_id adm1266_of_match[] = { + { .compatible = "adi,adm1266" }, + { } +}; +MODULE_DEVICE_TABLE(of, adm1266_of_match); + +static const struct i2c_device_id adm1266_id[] = { + { "adm1266", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adm1266_id); + +static struct i2c_driver adm1266_driver = { + .driver = { + .name = "adm1266", + .of_match_table = adm1266_of_match, + }, + .probe_new = adm1266_probe, + .id_table = adm1266_id, +}; + +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 new file mode 100644 index 000000000..b8543c06d --- /dev/null +++ b/drivers/hwmon/pmbus/adm1275.c @@ -0,0 +1,844 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for Analog Devices ADM1275 Hot-Swap Controller + * and Digital Power Monitor + * + * Copyright (c) 2011 Ericsson AB. + * Copyright (c) 2018 Guenter Roeck + */ + +#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/bitops.h> +#include <linux/bitfield.h> +#include <linux/log2.h> +#include "pmbus.h" + +enum chips { adm1075, adm1272, adm1275, adm1276, adm1278, adm1293, adm1294 }; + +#define ADM1275_MFR_STATUS_IOUT_WARN2 BIT(0) +#define ADM1293_MFR_STATUS_VAUX_UV_WARN BIT(5) +#define ADM1293_MFR_STATUS_VAUX_OV_WARN BIT(6) + +#define ADM1275_PEAK_IOUT 0xd0 +#define ADM1275_PEAK_VIN 0xd1 +#define ADM1275_PEAK_VOUT 0xd2 +#define ADM1275_PMON_CONFIG 0xd4 + +#define ADM1275_VIN_VOUT_SELECT BIT(6) +#define ADM1275_VRANGE BIT(5) +#define ADM1075_IRANGE_50 BIT(4) +#define ADM1075_IRANGE_25 BIT(3) +#define ADM1075_IRANGE_MASK (BIT(3) | BIT(4)) + +#define ADM1272_IRANGE BIT(0) + +#define ADM1278_TSFILT BIT(15) +#define ADM1278_TEMP1_EN BIT(3) +#define ADM1278_VIN_EN BIT(2) +#define ADM1278_VOUT_EN BIT(1) + +#define ADM1278_PMON_DEFCONFIG (ADM1278_VOUT_EN | ADM1278_TEMP1_EN | ADM1278_TSFILT) + +#define ADM1293_IRANGE_25 0 +#define ADM1293_IRANGE_50 BIT(6) +#define ADM1293_IRANGE_100 BIT(7) +#define ADM1293_IRANGE_200 (BIT(6) | BIT(7)) +#define ADM1293_IRANGE_MASK (BIT(6) | BIT(7)) + +#define ADM1293_VIN_SEL_012 BIT(2) +#define ADM1293_VIN_SEL_074 BIT(3) +#define ADM1293_VIN_SEL_210 (BIT(2) | BIT(3)) +#define ADM1293_VIN_SEL_MASK (BIT(2) | BIT(3)) + +#define ADM1293_VAUX_EN BIT(1) + +#define ADM1278_PEAK_TEMP 0xd7 +#define ADM1275_IOUT_WARN2_LIMIT 0xd7 +#define ADM1275_DEVICE_CONFIG 0xd8 + +#define ADM1275_IOUT_WARN2_SELECT BIT(4) + +#define ADM1276_PEAK_PIN 0xda +#define ADM1075_READ_VAUX 0xdd +#define ADM1075_VAUX_OV_WARN_LIMIT 0xde +#define ADM1075_VAUX_UV_WARN_LIMIT 0xdf +#define ADM1293_IOUT_MIN 0xe3 +#define ADM1293_PIN_MIN 0xe4 +#define ADM1075_VAUX_STATUS 0xf6 + +#define ADM1075_VAUX_OV_WARN BIT(7) +#define ADM1075_VAUX_UV_WARN BIT(6) + +#define ADM1275_VI_AVG_SHIFT 0 +#define ADM1275_VI_AVG_MASK GENMASK(ADM1275_VI_AVG_SHIFT + 2, \ + ADM1275_VI_AVG_SHIFT) +#define ADM1275_SAMPLES_AVG_MAX 128 + +#define ADM1278_PWR_AVG_SHIFT 11 +#define ADM1278_PWR_AVG_MASK GENMASK(ADM1278_PWR_AVG_SHIFT + 2, \ + ADM1278_PWR_AVG_SHIFT) +#define ADM1278_VI_AVG_SHIFT 8 +#define ADM1278_VI_AVG_MASK GENMASK(ADM1278_VI_AVG_SHIFT + 2, \ + ADM1278_VI_AVG_SHIFT) + +struct adm1275_data { + int id; + bool have_oc_fault; + bool have_uc_fault; + bool have_vout; + bool have_vaux_status; + bool have_mfr_vaux_status; + bool have_iout_min; + bool have_pin_min; + bool have_pin_max; + bool have_temp_max; + bool have_power_sampling; + struct pmbus_driver_info info; +}; + +#define to_adm1275_data(x) container_of(x, struct adm1275_data, info) + +struct coefficients { + s16 m; + s16 b; + s16 R; +}; + +static const struct coefficients adm1075_coefficients[] = { + [0] = { 27169, 0, -1 }, /* voltage */ + [1] = { 806, 20475, -1 }, /* current, irange25 */ + [2] = { 404, 20475, -1 }, /* current, irange50 */ + [3] = { 8549, 0, -1 }, /* power, irange25 */ + [4] = { 4279, 0, -1 }, /* power, irange50 */ +}; + +static const struct coefficients adm1272_coefficients[] = { + [0] = { 6770, 0, -2 }, /* voltage, vrange 60V */ + [1] = { 4062, 0, -2 }, /* voltage, vrange 100V */ + [2] = { 1326, 20480, -1 }, /* current, vsense range 15mV */ + [3] = { 663, 20480, -1 }, /* current, vsense range 30mV */ + [4] = { 3512, 0, -2 }, /* power, vrange 60V, irange 15mV */ + [5] = { 21071, 0, -3 }, /* power, vrange 100V, irange 15mV */ + [6] = { 17561, 0, -3 }, /* power, vrange 60V, irange 30mV */ + [7] = { 10535, 0, -3 }, /* power, vrange 100V, irange 30mV */ + [8] = { 42, 31871, -1 }, /* temperature */ + +}; + +static const struct coefficients adm1275_coefficients[] = { + [0] = { 19199, 0, -2 }, /* voltage, vrange set */ + [1] = { 6720, 0, -1 }, /* voltage, vrange not set */ + [2] = { 807, 20475, -1 }, /* current */ +}; + +static const struct coefficients adm1276_coefficients[] = { + [0] = { 19199, 0, -2 }, /* voltage, vrange set */ + [1] = { 6720, 0, -1 }, /* voltage, vrange not set */ + [2] = { 807, 20475, -1 }, /* current */ + [3] = { 6043, 0, -2 }, /* power, vrange set */ + [4] = { 2115, 0, -1 }, /* power, vrange not set */ +}; + +static const struct coefficients adm1278_coefficients[] = { + [0] = { 19599, 0, -2 }, /* voltage */ + [1] = { 800, 20475, -1 }, /* current */ + [2] = { 6123, 0, -2 }, /* power */ + [3] = { 42, 31880, -1 }, /* temperature */ +}; + +static const struct coefficients adm1293_coefficients[] = { + [0] = { 3333, -1, 0 }, /* voltage, vrange 1.2V */ + [1] = { 5552, -5, -1 }, /* voltage, vrange 7.4V */ + [2] = { 19604, -50, -2 }, /* voltage, vrange 21V */ + [3] = { 8000, -100, -2 }, /* current, irange25 */ + [4] = { 4000, -100, -2 }, /* current, irange50 */ + [5] = { 20000, -1000, -3 }, /* current, irange100 */ + [6] = { 10000, -1000, -3 }, /* current, irange200 */ + [7] = { 10417, 0, -1 }, /* power, 1.2V, irange25 */ + [8] = { 5208, 0, -1 }, /* power, 1.2V, irange50 */ + [9] = { 26042, 0, -2 }, /* power, 1.2V, irange100 */ + [10] = { 13021, 0, -2 }, /* power, 1.2V, irange200 */ + [11] = { 17351, 0, -2 }, /* power, 7.4V, irange25 */ + [12] = { 8676, 0, -2 }, /* power, 7.4V, irange50 */ + [13] = { 4338, 0, -2 }, /* power, 7.4V, irange100 */ + [14] = { 21689, 0, -3 }, /* power, 7.4V, irange200 */ + [15] = { 6126, 0, -2 }, /* power, 21V, irange25 */ + [16] = { 30631, 0, -3 }, /* power, 21V, irange50 */ + [17] = { 15316, 0, -3 }, /* power, 21V, irange100 */ + [18] = { 7658, 0, -3 }, /* power, 21V, irange200 */ +}; + +static int adm1275_read_pmon_config(const struct adm1275_data *data, + struct i2c_client *client, bool is_power) +{ + int shift, ret; + u16 mask; + + /* + * The PMON configuration register is a 16-bit register only on chips + * supporting power average sampling. On other chips it is an 8-bit + * register. + */ + if (data->have_power_sampling) { + ret = i2c_smbus_read_word_data(client, ADM1275_PMON_CONFIG); + mask = is_power ? ADM1278_PWR_AVG_MASK : ADM1278_VI_AVG_MASK; + shift = is_power ? ADM1278_PWR_AVG_SHIFT : ADM1278_VI_AVG_SHIFT; + } else { + ret = i2c_smbus_read_byte_data(client, ADM1275_PMON_CONFIG); + mask = ADM1275_VI_AVG_MASK; + shift = ADM1275_VI_AVG_SHIFT; + } + if (ret < 0) + return ret; + + return (ret & mask) >> shift; +} + +static int adm1275_write_pmon_config(const struct adm1275_data *data, + struct i2c_client *client, + bool is_power, u16 word) +{ + int shift, ret; + u16 mask; + + if (data->have_power_sampling) { + ret = i2c_smbus_read_word_data(client, ADM1275_PMON_CONFIG); + mask = is_power ? ADM1278_PWR_AVG_MASK : ADM1278_VI_AVG_MASK; + shift = is_power ? ADM1278_PWR_AVG_SHIFT : ADM1278_VI_AVG_SHIFT; + } else { + ret = i2c_smbus_read_byte_data(client, ADM1275_PMON_CONFIG); + mask = ADM1275_VI_AVG_MASK; + shift = ADM1275_VI_AVG_SHIFT; + } + if (ret < 0) + return ret; + + word = (ret & ~mask) | ((word << shift) & mask); + if (data->have_power_sampling) + ret = i2c_smbus_write_word_data(client, ADM1275_PMON_CONFIG, + word); + else + ret = i2c_smbus_write_byte_data(client, ADM1275_PMON_CONFIG, + word); + + return ret; +} + +static int adm1275_read_word_data(struct i2c_client *client, int page, + int phase, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + const struct adm1275_data *data = to_adm1275_data(info); + int ret = 0; + + if (page > 0) + return -ENXIO; + + switch (reg) { + case PMBUS_IOUT_UC_FAULT_LIMIT: + if (!data->have_uc_fault) + return -ENXIO; + ret = pmbus_read_word_data(client, 0, 0xff, + ADM1275_IOUT_WARN2_LIMIT); + break; + case PMBUS_IOUT_OC_FAULT_LIMIT: + if (!data->have_oc_fault) + return -ENXIO; + ret = pmbus_read_word_data(client, 0, 0xff, + ADM1275_IOUT_WARN2_LIMIT); + break; + case PMBUS_VOUT_OV_WARN_LIMIT: + if (data->have_vout) + return -ENODATA; + ret = pmbus_read_word_data(client, 0, 0xff, + ADM1075_VAUX_OV_WARN_LIMIT); + break; + case PMBUS_VOUT_UV_WARN_LIMIT: + if (data->have_vout) + return -ENODATA; + ret = pmbus_read_word_data(client, 0, 0xff, + ADM1075_VAUX_UV_WARN_LIMIT); + break; + case PMBUS_READ_VOUT: + if (data->have_vout) + return -ENODATA; + ret = pmbus_read_word_data(client, 0, 0xff, + ADM1075_READ_VAUX); + break; + case PMBUS_VIRT_READ_IOUT_MIN: + if (!data->have_iout_min) + return -ENXIO; + ret = pmbus_read_word_data(client, 0, 0xff, + ADM1293_IOUT_MIN); + break; + case PMBUS_VIRT_READ_IOUT_MAX: + ret = pmbus_read_word_data(client, 0, 0xff, + ADM1275_PEAK_IOUT); + break; + case PMBUS_VIRT_READ_VOUT_MAX: + ret = pmbus_read_word_data(client, 0, 0xff, + ADM1275_PEAK_VOUT); + break; + case PMBUS_VIRT_READ_VIN_MAX: + ret = pmbus_read_word_data(client, 0, 0xff, + ADM1275_PEAK_VIN); + break; + case PMBUS_VIRT_READ_PIN_MIN: + if (!data->have_pin_min) + return -ENXIO; + ret = pmbus_read_word_data(client, 0, 0xff, + ADM1293_PIN_MIN); + break; + case PMBUS_VIRT_READ_PIN_MAX: + if (!data->have_pin_max) + return -ENXIO; + ret = pmbus_read_word_data(client, 0, 0xff, + ADM1276_PEAK_PIN); + break; + case PMBUS_VIRT_READ_TEMP_MAX: + if (!data->have_temp_max) + return -ENXIO; + ret = pmbus_read_word_data(client, 0, 0xff, + ADM1278_PEAK_TEMP); + break; + case PMBUS_VIRT_RESET_IOUT_HISTORY: + case PMBUS_VIRT_RESET_VOUT_HISTORY: + case PMBUS_VIRT_RESET_VIN_HISTORY: + break; + case PMBUS_VIRT_RESET_PIN_HISTORY: + if (!data->have_pin_max) + return -ENXIO; + break; + case PMBUS_VIRT_RESET_TEMP_HISTORY: + if (!data->have_temp_max) + return -ENXIO; + break; + case PMBUS_VIRT_POWER_SAMPLES: + if (!data->have_power_sampling) + return -ENXIO; + ret = adm1275_read_pmon_config(data, client, true); + if (ret < 0) + break; + ret = BIT(ret); + break; + case PMBUS_VIRT_IN_SAMPLES: + case PMBUS_VIRT_CURR_SAMPLES: + ret = adm1275_read_pmon_config(data, client, false); + if (ret < 0) + break; + ret = BIT(ret); + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int adm1275_write_word_data(struct i2c_client *client, int page, int reg, + u16 word) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + const struct adm1275_data *data = to_adm1275_data(info); + int ret; + + if (page > 0) + return -ENXIO; + + switch (reg) { + case PMBUS_IOUT_UC_FAULT_LIMIT: + case PMBUS_IOUT_OC_FAULT_LIMIT: + ret = pmbus_write_word_data(client, 0, ADM1275_IOUT_WARN2_LIMIT, + word); + break; + case PMBUS_VIRT_RESET_IOUT_HISTORY: + ret = pmbus_write_word_data(client, 0, ADM1275_PEAK_IOUT, 0); + if (!ret && data->have_iout_min) + ret = pmbus_write_word_data(client, 0, + ADM1293_IOUT_MIN, 0); + break; + case PMBUS_VIRT_RESET_VOUT_HISTORY: + ret = pmbus_write_word_data(client, 0, ADM1275_PEAK_VOUT, 0); + break; + case PMBUS_VIRT_RESET_VIN_HISTORY: + ret = pmbus_write_word_data(client, 0, ADM1275_PEAK_VIN, 0); + break; + case PMBUS_VIRT_RESET_PIN_HISTORY: + ret = pmbus_write_word_data(client, 0, ADM1276_PEAK_PIN, 0); + if (!ret && data->have_pin_min) + ret = pmbus_write_word_data(client, 0, + ADM1293_PIN_MIN, 0); + break; + case PMBUS_VIRT_RESET_TEMP_HISTORY: + ret = pmbus_write_word_data(client, 0, ADM1278_PEAK_TEMP, 0); + break; + case PMBUS_VIRT_POWER_SAMPLES: + if (!data->have_power_sampling) + return -ENXIO; + word = clamp_val(word, 1, ADM1275_SAMPLES_AVG_MAX); + ret = adm1275_write_pmon_config(data, client, true, + ilog2(word)); + break; + case PMBUS_VIRT_IN_SAMPLES: + case PMBUS_VIRT_CURR_SAMPLES: + word = clamp_val(word, 1, ADM1275_SAMPLES_AVG_MAX); + ret = adm1275_write_pmon_config(data, client, false, + ilog2(word)); + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int adm1275_read_byte_data(struct i2c_client *client, int page, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + const struct adm1275_data *data = to_adm1275_data(info); + int mfr_status, ret; + + if (page > 0) + return -ENXIO; + + switch (reg) { + case PMBUS_STATUS_IOUT: + ret = pmbus_read_byte_data(client, page, PMBUS_STATUS_IOUT); + if (ret < 0) + break; + if (!data->have_oc_fault && !data->have_uc_fault) + break; + mfr_status = pmbus_read_byte_data(client, page, + PMBUS_STATUS_MFR_SPECIFIC); + if (mfr_status < 0) + return mfr_status; + if (mfr_status & ADM1275_MFR_STATUS_IOUT_WARN2) { + ret |= data->have_oc_fault ? + PB_IOUT_OC_FAULT : PB_IOUT_UC_FAULT; + } + break; + case PMBUS_STATUS_VOUT: + if (data->have_vout) + return -ENODATA; + ret = 0; + if (data->have_vaux_status) { + mfr_status = pmbus_read_byte_data(client, 0, + ADM1075_VAUX_STATUS); + if (mfr_status < 0) + return mfr_status; + if (mfr_status & ADM1075_VAUX_OV_WARN) + ret |= PB_VOLTAGE_OV_WARNING; + if (mfr_status & ADM1075_VAUX_UV_WARN) + ret |= PB_VOLTAGE_UV_WARNING; + } else if (data->have_mfr_vaux_status) { + mfr_status = pmbus_read_byte_data(client, page, + PMBUS_STATUS_MFR_SPECIFIC); + if (mfr_status < 0) + return mfr_status; + if (mfr_status & ADM1293_MFR_STATUS_VAUX_OV_WARN) + ret |= PB_VOLTAGE_OV_WARNING; + if (mfr_status & ADM1293_MFR_STATUS_VAUX_UV_WARN) + ret |= PB_VOLTAGE_UV_WARNING; + } + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static const struct i2c_device_id adm1275_id[] = { + { "adm1075", adm1075 }, + { "adm1272", adm1272 }, + { "adm1275", adm1275 }, + { "adm1276", adm1276 }, + { "adm1278", adm1278 }, + { "adm1293", adm1293 }, + { "adm1294", adm1294 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adm1275_id); + +/* Enable VOUT & TEMP1 if not enabled (disabled by default) */ +static int adm1275_enable_vout_temp(struct i2c_client *client, int config) +{ + int ret; + + if ((config & ADM1278_PMON_DEFCONFIG) != ADM1278_PMON_DEFCONFIG) { + config |= ADM1278_PMON_DEFCONFIG; + ret = i2c_smbus_write_word_data(client, ADM1275_PMON_CONFIG, config); + if (ret < 0) { + dev_err(&client->dev, "Failed to enable VOUT/TEMP1 monitoring\n"); + return ret; + } + } + return 0; +} + +static int adm1275_probe(struct i2c_client *client) +{ + s32 (*config_read_fn)(const struct i2c_client *client, u8 reg); + u8 block_buffer[I2C_SMBUS_BLOCK_MAX + 1]; + int config, device_config; + int ret; + struct pmbus_driver_info *info; + struct adm1275_data *data; + const struct i2c_device_id *mid; + const struct coefficients *coefficients; + int vindex = -1, voindex = -1, cindex = -1, pindex = -1; + int tindex = -1; + u32 shunt; + u32 avg; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_BYTE_DATA + | I2C_FUNC_SMBUS_BLOCK_DATA)) + return -ENODEV; + + ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, block_buffer); + if (ret < 0) { + dev_err(&client->dev, "Failed to read Manufacturer ID\n"); + return ret; + } + if (ret != 3 || strncmp(block_buffer, "ADI", 3)) { + dev_err(&client->dev, "Unsupported Manufacturer ID\n"); + return -ENODEV; + } + + ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, block_buffer); + if (ret < 0) { + dev_err(&client->dev, "Failed to read Manufacturer Model\n"); + return ret; + } + for (mid = adm1275_id; mid->name[0]; mid++) { + if (!strncasecmp(mid->name, block_buffer, strlen(mid->name))) + break; + } + if (!mid->name[0]) { + dev_err(&client->dev, "Unsupported device\n"); + return -ENODEV; + } + + if (strcmp(client->name, mid->name) != 0) + dev_notice(&client->dev, + "Device mismatch: Configured %s, detected %s\n", + client->name, mid->name); + + if (mid->driver_data == adm1272 || mid->driver_data == adm1278 || + mid->driver_data == adm1293 || mid->driver_data == adm1294) + config_read_fn = i2c_smbus_read_word_data; + else + config_read_fn = i2c_smbus_read_byte_data; + config = config_read_fn(client, ADM1275_PMON_CONFIG); + if (config < 0) + return config; + + device_config = config_read_fn(client, ADM1275_DEVICE_CONFIG); + if (device_config < 0) + return device_config; + + data = devm_kzalloc(&client->dev, sizeof(struct adm1275_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + if (of_property_read_u32(client->dev.of_node, + "shunt-resistor-micro-ohms", &shunt)) + shunt = 1000; /* 1 mOhm if not set via DT */ + + if (shunt == 0) + return -EINVAL; + + data->id = mid->driver_data; + + info = &data->info; + + info->pages = 1; + info->format[PSC_VOLTAGE_IN] = direct; + info->format[PSC_VOLTAGE_OUT] = direct; + info->format[PSC_CURRENT_OUT] = direct; + info->format[PSC_POWER] = direct; + info->format[PSC_TEMPERATURE] = direct; + info->func[0] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_SAMPLES; + + info->read_word_data = adm1275_read_word_data; + info->read_byte_data = adm1275_read_byte_data; + info->write_word_data = adm1275_write_word_data; + + switch (data->id) { + case adm1075: + if (device_config & ADM1275_IOUT_WARN2_SELECT) + data->have_oc_fault = true; + else + data->have_uc_fault = true; + data->have_pin_max = true; + data->have_vaux_status = true; + + coefficients = adm1075_coefficients; + vindex = 0; + switch (config & ADM1075_IRANGE_MASK) { + case ADM1075_IRANGE_25: + cindex = 1; + pindex = 3; + break; + case ADM1075_IRANGE_50: + cindex = 2; + pindex = 4; + break; + default: + dev_err(&client->dev, "Invalid input current range"); + break; + } + + info->func[0] |= PMBUS_HAVE_VIN | PMBUS_HAVE_PIN + | PMBUS_HAVE_STATUS_INPUT; + if (config & ADM1275_VIN_VOUT_SELECT) + info->func[0] |= + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; + break; + case adm1272: + data->have_vout = true; + data->have_pin_max = true; + data->have_temp_max = true; + data->have_power_sampling = true; + + coefficients = adm1272_coefficients; + vindex = (config & ADM1275_VRANGE) ? 1 : 0; + cindex = (config & ADM1272_IRANGE) ? 3 : 2; + /* pindex depends on the combination of the above */ + switch (config & (ADM1275_VRANGE | ADM1272_IRANGE)) { + case 0: + default: + pindex = 4; + break; + case ADM1275_VRANGE: + pindex = 5; + break; + case ADM1272_IRANGE: + pindex = 6; + break; + case ADM1275_VRANGE | ADM1272_IRANGE: + pindex = 7; + break; + } + tindex = 8; + + info->func[0] |= PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT | + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + + ret = adm1275_enable_vout_temp(client, config); + if (ret) + return ret; + + if (config & ADM1278_VIN_EN) + info->func[0] |= PMBUS_HAVE_VIN; + break; + case adm1275: + if (device_config & ADM1275_IOUT_WARN2_SELECT) + data->have_oc_fault = true; + else + data->have_uc_fault = true; + data->have_vout = true; + + coefficients = adm1275_coefficients; + vindex = (config & ADM1275_VRANGE) ? 0 : 1; + cindex = 2; + + if (config & ADM1275_VIN_VOUT_SELECT) + info->func[0] |= + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; + else + info->func[0] |= + PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT; + break; + case adm1276: + if (device_config & ADM1275_IOUT_WARN2_SELECT) + data->have_oc_fault = true; + else + data->have_uc_fault = true; + data->have_vout = true; + data->have_pin_max = true; + + coefficients = adm1276_coefficients; + vindex = (config & ADM1275_VRANGE) ? 0 : 1; + cindex = 2; + pindex = (config & ADM1275_VRANGE) ? 3 : 4; + + info->func[0] |= PMBUS_HAVE_VIN | PMBUS_HAVE_PIN + | PMBUS_HAVE_STATUS_INPUT; + if (config & ADM1275_VIN_VOUT_SELECT) + info->func[0] |= + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; + break; + case adm1278: + data->have_vout = true; + data->have_pin_max = true; + data->have_temp_max = true; + data->have_power_sampling = true; + + coefficients = adm1278_coefficients; + vindex = 0; + cindex = 1; + pindex = 2; + tindex = 3; + + info->func[0] |= PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT | + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + + ret = adm1275_enable_vout_temp(client, config); + if (ret) + return ret; + + if (config & ADM1278_VIN_EN) + info->func[0] |= PMBUS_HAVE_VIN; + break; + case adm1293: + case adm1294: + data->have_iout_min = true; + data->have_pin_min = true; + data->have_pin_max = true; + data->have_mfr_vaux_status = true; + data->have_power_sampling = true; + + coefficients = adm1293_coefficients; + + voindex = 0; + switch (config & ADM1293_VIN_SEL_MASK) { + case ADM1293_VIN_SEL_012: /* 1.2V */ + vindex = 0; + break; + case ADM1293_VIN_SEL_074: /* 7.4V */ + vindex = 1; + break; + case ADM1293_VIN_SEL_210: /* 21V */ + vindex = 2; + break; + default: /* disabled */ + break; + } + + switch (config & ADM1293_IRANGE_MASK) { + case ADM1293_IRANGE_25: + cindex = 3; + break; + case ADM1293_IRANGE_50: + cindex = 4; + break; + case ADM1293_IRANGE_100: + cindex = 5; + break; + case ADM1293_IRANGE_200: + cindex = 6; + break; + } + + if (vindex >= 0) + pindex = 7 + vindex * 4 + (cindex - 3); + + if (config & ADM1293_VAUX_EN) + info->func[0] |= + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; + + info->func[0] |= PMBUS_HAVE_PIN | + PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT; + + break; + default: + dev_err(&client->dev, "Unsupported device\n"); + return -ENODEV; + } + + if (data->have_power_sampling && + of_property_read_u32(client->dev.of_node, + "adi,power-sample-average", &avg) == 0) { + if (!avg || avg > ADM1275_SAMPLES_AVG_MAX || + BIT(__fls(avg)) != avg) { + dev_err(&client->dev, + "Invalid number of power samples"); + return -EINVAL; + } + ret = adm1275_write_pmon_config(data, client, true, + ilog2(avg)); + if (ret < 0) { + dev_err(&client->dev, + "Setting power sample averaging failed with error %d", + ret); + return ret; + } + } + + if (of_property_read_u32(client->dev.of_node, + "adi,volt-curr-sample-average", &avg) == 0) { + if (!avg || avg > ADM1275_SAMPLES_AVG_MAX || + BIT(__fls(avg)) != avg) { + dev_err(&client->dev, + "Invalid number of voltage/current samples"); + return -EINVAL; + } + ret = adm1275_write_pmon_config(data, client, false, + ilog2(avg)); + if (ret < 0) { + dev_err(&client->dev, + "Setting voltage and current sample averaging failed with error %d", + ret); + return ret; + } + } + + if (voindex < 0) + voindex = vindex; + if (vindex >= 0) { + info->m[PSC_VOLTAGE_IN] = coefficients[vindex].m; + info->b[PSC_VOLTAGE_IN] = coefficients[vindex].b; + info->R[PSC_VOLTAGE_IN] = coefficients[vindex].R; + } + if (voindex >= 0) { + info->m[PSC_VOLTAGE_OUT] = coefficients[voindex].m; + info->b[PSC_VOLTAGE_OUT] = coefficients[voindex].b; + info->R[PSC_VOLTAGE_OUT] = coefficients[voindex].R; + } + if (cindex >= 0) { + /* Scale current with sense resistor value */ + info->m[PSC_CURRENT_OUT] = + coefficients[cindex].m * shunt / 1000; + info->b[PSC_CURRENT_OUT] = coefficients[cindex].b; + info->R[PSC_CURRENT_OUT] = coefficients[cindex].R; + } + if (pindex >= 0) { + info->m[PSC_POWER] = + coefficients[pindex].m * shunt / 1000; + info->b[PSC_POWER] = coefficients[pindex].b; + info->R[PSC_POWER] = coefficients[pindex].R; + } + if (tindex >= 0) { + info->m[PSC_TEMPERATURE] = coefficients[tindex].m; + info->b[PSC_TEMPERATURE] = coefficients[tindex].b; + info->R[PSC_TEMPERATURE] = coefficients[tindex].R; + } + + return pmbus_do_probe(client, info); +} + +static struct i2c_driver adm1275_driver = { + .driver = { + .name = "adm1275", + }, + .probe_new = adm1275_probe, + .id_table = adm1275_id, +}; + +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 new file mode 100644 index 000000000..61c195f8f --- /dev/null +++ b/drivers/hwmon/pmbus/bel-pfe.c @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Hardware monitoring driver for BEL PFE family power supplies. + * + * Copyright (c) 2019 Facebook Inc. + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pmbus.h> + +#include "pmbus.h" + +enum chips {pfe1100, pfe3000}; + +/* + * Disable status check because some devices report communication error + * (invalid command) for VOUT_MODE command (0x20) although the correct + * VOUT_MODE (0x16) is returned: it leads to incorrect exponent in linear + * mode. + * This affects both pfe3000 and pfe1100. + */ +static struct pmbus_platform_data pfe_plat_data = { + .flags = PMBUS_SKIP_STATUS_CHECK, +}; + +static struct pmbus_driver_info pfe_driver_info[] = { + [pfe1100] = { + .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_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_POUT | + PMBUS_HAVE_VIN | PMBUS_HAVE_IIN | + PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | + PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_FAN12, + }, + + [pfe3000] = { + .pages = 7, + .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, + + /* Page 0: V1. */ + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_POUT | PMBUS_HAVE_FAN12 | + PMBUS_HAVE_VIN | PMBUS_HAVE_IIN | + PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | + PMBUS_HAVE_TEMP3 | PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_VCAP, + + /* Page 1: Vsb. */ + .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT | + PMBUS_HAVE_POUT, + + /* + * Page 2: V1 Ishare. + * Page 3: Reserved. + * Page 4: V1 Cathode. + * Page 5: Vsb Cathode. + * Page 6: V1 Sense. + */ + .func[2] = PMBUS_HAVE_VOUT, + .func[4] = PMBUS_HAVE_VOUT, + .func[5] = PMBUS_HAVE_VOUT, + .func[6] = PMBUS_HAVE_VOUT, + }, +}; + +static const struct i2c_device_id pfe_device_id[]; + +static int pfe_pmbus_probe(struct i2c_client *client) +{ + int model; + + model = (int)i2c_match_id(pfe_device_id, client)->driver_data; + client->dev.platform_data = &pfe_plat_data; + + /* + * PFE3000-12-069RA devices may not stay in page 0 during device + * probe which leads to probe failure (read status word failed). + * So let's set the device to page 0 at the beginning. + */ + if (model == pfe3000) + i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); + + return pmbus_do_probe(client, &pfe_driver_info[model]); +} + +static const struct i2c_device_id pfe_device_id[] = { + {"pfe1100", pfe1100}, + {"pfe3000", pfe3000}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, pfe_device_id); + +static struct i2c_driver pfe_pmbus_driver = { + .driver = { + .name = "bel-pfe", + }, + .probe_new = pfe_pmbus_probe, + .id_table = pfe_device_id, +}; + +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 000000000..f2d4e378a --- /dev/null +++ b/drivers/hwmon/pmbus/bpa-rs600.c @@ -0,0 +1,208 @@ +// 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" + +enum chips { bpa_rs600, bpd_rs600 }; + +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; +} + +/* + * The BPA-RS600 violates the PMBus spec. Specifically it treats the + * mantissa as unsigned. Deal with this here to allow the PMBus core + * to work with correctly encoded data. + */ +static int bpa_rs600_read_vin(struct i2c_client *client) +{ + int ret, exponent, mantissa; + + ret = pmbus_read_word_data(client, 0, 0xff, PMBUS_READ_VIN); + if (ret < 0) + return ret; + + if (ret & BIT(10)) { + exponent = ret >> 11; + mantissa = ret & 0x7ff; + + exponent++; + mantissa >>= 1; + + ret = (exponent << 11) | mantissa; + } + + return ret; +} + +/* + * Firmware V5.70 incorrectly reports 1640W for MFR_PIN_MAX. + * Deal with this by returning a sensible value. + */ +static int bpa_rs600_read_pin_max(struct i2c_client *client) +{ + int ret; + + ret = pmbus_read_word_data(client, 0, 0xff, PMBUS_MFR_PIN_MAX); + if (ret < 0) + return ret; + + /* Detect invalid 1640W (linear encoding) */ + if (ret == 0x0b34) + /* Report 700W (linear encoding) */ + return 0x095e; + + 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: + case PMBUS_VIN_OV_WARN_LIMIT: + case PMBUS_VOUT_UV_WARN_LIMIT: + case PMBUS_VOUT_OV_WARN_LIMIT: + case PMBUS_IIN_OC_WARN_LIMIT: + case PMBUS_IOUT_OC_WARN_LIMIT: + case PMBUS_PIN_OP_WARN_LIMIT: + case PMBUS_POUT_OP_WARN_LIMIT: + 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; + case PMBUS_READ_VIN: + ret = bpa_rs600_read_vin(client); + break; + case PMBUS_MFR_PIN_MAX: + ret = bpa_rs600_read_pin_max(client); + 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 const struct i2c_device_id bpa_rs600_id[] = { + { "bpa-rs600", bpa_rs600 }, + { "bpd-rs600", bpd_rs600 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, bpa_rs600_id); + +static int bpa_rs600_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + u8 buf[I2C_SMBUS_BLOCK_MAX + 1]; + int ret; + const struct i2c_device_id *mid; + + 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; + } + + for (mid = bpa_rs600_id; mid->name[0]; mid++) { + if (!strncasecmp(buf, mid->name, strlen(mid->name))) + break; + } + if (!mid->name[0]) { + 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 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/delta-ahe50dc-fan.c b/drivers/hwmon/pmbus/delta-ahe50dc-fan.c new file mode 100644 index 000000000..f546f0c12 --- /dev/null +++ b/drivers/hwmon/pmbus/delta-ahe50dc-fan.c @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Delta AHE-50DC power shelf fan control module driver + * + * Copyright 2021 Zev Weiss <zev@bewilderbeest.net> + */ + +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pmbus.h> + +#include "pmbus.h" + +#define AHE50DC_PMBUS_READ_TEMP4 0xd0 + +static int ahe50dc_fan_write_byte(struct i2c_client *client, int page, u8 value) +{ + /* + * The CLEAR_FAULTS operation seems to sometimes (unpredictably, perhaps + * 5% of the time or so) trigger a problematic phenomenon in which the + * fan speeds surge momentarily and at least some (perhaps all?) of the + * system's power outputs experience a glitch. + * + * However, according to Delta it should be OK to simply not send any + * CLEAR_FAULTS commands (the device doesn't seem to be capable of + * reporting any faults anyway), so just blackhole them unconditionally. + */ + return value == PMBUS_CLEAR_FAULTS ? -EOPNOTSUPP : -ENODATA; +} + +static int ahe50dc_fan_read_word_data(struct i2c_client *client, int page, int phase, int reg) +{ + /* temp1 in (virtual) page 1 is remapped to mfr-specific temp4 */ + if (page == 1) { + if (reg == PMBUS_READ_TEMPERATURE_1) + return i2c_smbus_read_word_data(client, AHE50DC_PMBUS_READ_TEMP4); + return -EOPNOTSUPP; + } + + /* + * There's a fairly limited set of commands this device actually + * supports, so here we block attempts to read anything else (which + * return 0xffff and would cause confusion elsewhere). + */ + switch (reg) { + case PMBUS_STATUS_WORD: + case PMBUS_FAN_COMMAND_1: + case PMBUS_FAN_COMMAND_2: + case PMBUS_FAN_COMMAND_3: + case PMBUS_FAN_COMMAND_4: + case PMBUS_STATUS_FAN_12: + case PMBUS_STATUS_FAN_34: + case PMBUS_READ_VIN: + case PMBUS_READ_TEMPERATURE_1: + case PMBUS_READ_TEMPERATURE_2: + case PMBUS_READ_TEMPERATURE_3: + case PMBUS_READ_FAN_SPEED_1: + case PMBUS_READ_FAN_SPEED_2: + case PMBUS_READ_FAN_SPEED_3: + case PMBUS_READ_FAN_SPEED_4: + return -ENODATA; + default: + return -EOPNOTSUPP; + } +} + +static struct pmbus_driver_info ahe50dc_fan_info = { + .pages = 2, + .format[PSC_FAN] = direct, + .format[PSC_TEMPERATURE] = direct, + .format[PSC_VOLTAGE_IN] = direct, + .m[PSC_FAN] = 1, + .b[PSC_FAN] = 0, + .R[PSC_FAN] = 0, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 1, + .m[PSC_VOLTAGE_IN] = 1, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 3, + .func[0] = PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_TEMP3 | + PMBUS_HAVE_VIN | PMBUS_HAVE_FAN12 | PMBUS_HAVE_FAN34 | + PMBUS_HAVE_STATUS_FAN12 | PMBUS_HAVE_STATUS_FAN34 | PMBUS_PAGE_VIRTUAL, + .func[1] = PMBUS_HAVE_TEMP | PMBUS_PAGE_VIRTUAL, + .write_byte = ahe50dc_fan_write_byte, + .read_word_data = ahe50dc_fan_read_word_data, +}; + +/* + * CAPABILITY returns 0xff, which appears to be this device's way indicating + * it doesn't support something (and if we enable I2C_CLIENT_PEC on seeing bit + * 7 being set it generates bad PECs, so let's not go there). + */ +static struct pmbus_platform_data ahe50dc_fan_data = { + .flags = PMBUS_NO_CAPABILITY, +}; + +static int ahe50dc_fan_probe(struct i2c_client *client) +{ + client->dev.platform_data = &ahe50dc_fan_data; + return pmbus_do_probe(client, &ahe50dc_fan_info); +} + +static const struct i2c_device_id ahe50dc_fan_id[] = { + { "ahe50dc_fan" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ahe50dc_fan_id); + +static const struct of_device_id __maybe_unused ahe50dc_fan_of_match[] = { + { .compatible = "delta,ahe50dc-fan" }, + { } +}; +MODULE_DEVICE_TABLE(of, ahe50dc_fan_of_match); + +static struct i2c_driver ahe50dc_fan_driver = { + .driver = { + .name = "ahe50dc_fan", + .of_match_table = of_match_ptr(ahe50dc_fan_of_match), + }, + .probe_new = ahe50dc_fan_probe, + .id_table = ahe50dc_fan_id, +}; +module_i2c_driver(ahe50dc_fan_driver); + +MODULE_AUTHOR("Zev Weiss <zev@bewilderbeest.net>"); +MODULE_DESCRIPTION("Driver for Delta AHE-50DC power shelf fan control module"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/dps920ab.c b/drivers/hwmon/pmbus/dps920ab.c new file mode 100644 index 000000000..d3941f6eb --- /dev/null +++ b/drivers/hwmon/pmbus/dps920ab.c @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Delta DPS920AB PSU + * + * Copyright (C) 2021 Delta Networks, Inc. + * Copyright (C) 2021 Sartura Ltd. + */ + +#include <linux/debugfs.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include "pmbus.h" + +struct dps920ab_data { + char *mfr_model; + char *mfr_id; +}; + +static int dps920ab_read_word_data(struct i2c_client *client, int page, int phase, int reg) +{ + /* + * This masks commands which are not supported. + * PSU advertises that all features are supported, + * in reality that unfortunately is not true. + * So enable only those that the datasheet confirms. + */ + switch (reg) { + case PMBUS_FAN_COMMAND_1: + case PMBUS_IOUT_OC_WARN_LIMIT: + case PMBUS_STATUS_WORD: + case PMBUS_READ_VIN: + case PMBUS_READ_IIN: + case PMBUS_READ_VOUT: + case PMBUS_READ_IOUT: + case PMBUS_READ_TEMPERATURE_1: + case PMBUS_READ_TEMPERATURE_2: + case PMBUS_READ_TEMPERATURE_3: + case PMBUS_READ_FAN_SPEED_1: + case PMBUS_READ_POUT: + case PMBUS_READ_PIN: + case PMBUS_MFR_VOUT_MIN: + case PMBUS_MFR_VOUT_MAX: + case PMBUS_MFR_IOUT_MAX: + case PMBUS_MFR_POUT_MAX: + return pmbus_read_word_data(client, page, phase, reg); + default: + return -ENXIO; + } +} + +static int dps920ab_write_word_data(struct i2c_client *client, int page, int reg, + u16 word) +{ + /* + * This masks commands which are not supported. + * PSU only has one R/W register and that is + * for the fan. + */ + switch (reg) { + case PMBUS_FAN_COMMAND_1: + return pmbus_write_word_data(client, page, reg, word); + default: + return -EACCES; + } +} + +static struct pmbus_driver_info dps920ab_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_FAN] = linear, + .format[PSC_TEMPERATURE] = linear, + + .func[0] = + PMBUS_HAVE_VIN | PMBUS_HAVE_IIN | PMBUS_HAVE_PIN | + PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | PMBUS_HAVE_POUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_TEMP3 | + PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12 | + PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP, + .read_word_data = dps920ab_read_word_data, + .write_word_data = dps920ab_write_word_data, +}; + +static int dps920ab_mfr_id_show(struct seq_file *s, void *data) +{ + struct dps920ab_data *priv = s->private; + + seq_printf(s, "%s\n", priv->mfr_id); + + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(dps920ab_mfr_id); + +static int dps920ab_mfr_model_show(struct seq_file *s, void *data) +{ + struct dps920ab_data *priv = s->private; + + seq_printf(s, "%s\n", priv->mfr_model); + + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(dps920ab_mfr_model); + +static void dps920ab_init_debugfs(struct dps920ab_data *data, struct i2c_client *client) +{ + struct dentry *debugfs_dir; + struct dentry *root; + + root = pmbus_get_debugfs_dir(client); + if (!root) + return; + + debugfs_dir = debugfs_create_dir(client->name, root); + + debugfs_create_file("mfr_id", + 0400, + debugfs_dir, + data, + &dps920ab_mfr_id_fops); + + debugfs_create_file("mfr_model", + 0400, + debugfs_dir, + data, + &dps920ab_mfr_model_fops); +} + +static int dps920ab_probe(struct i2c_client *client) +{ + u8 buf[I2C_SMBUS_BLOCK_MAX + 1]; + struct dps920ab_data *data; + int ret; + + data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf); + if (ret < 0) { + dev_err(&client->dev, "Failed to read Manufacturer ID\n"); + return ret; + } + + buf[ret] = '\0'; + if (ret != 5 || strncmp(buf, "DELTA", 5)) { + buf[ret] = '\0'; + dev_err(&client->dev, "Unsupported Manufacturer ID '%s'\n", buf); + return -ENODEV; + } + data->mfr_id = devm_kstrdup(&client->dev, buf, GFP_KERNEL); + if (!data->mfr_id) + return -ENOMEM; + + ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf); + if (ret < 0) { + dev_err(&client->dev, "Failed to read Manufacturer Model\n"); + return ret; + } + + buf[ret] = '\0'; + if (ret != 11 || strncmp(buf, "DPS-920AB", 9)) { + dev_err(&client->dev, "Unsupported Manufacturer Model '%s'\n", buf); + return -ENODEV; + } + data->mfr_model = devm_kstrdup(&client->dev, buf, GFP_KERNEL); + if (!data->mfr_model) + return -ENOMEM; + + ret = pmbus_do_probe(client, &dps920ab_info); + if (ret) + return ret; + + dps920ab_init_debugfs(data, client); + + return 0; +} + +static const struct of_device_id __maybe_unused dps920ab_of_match[] = { + { .compatible = "delta,dps920ab", }, + {} +}; + +MODULE_DEVICE_TABLE(of, dps920ab_of_match); + +static struct i2c_driver dps920ab_driver = { + .driver = { + .name = "dps920ab", + .of_match_table = of_match_ptr(dps920ab_of_match), + }, + .probe_new = dps920ab_probe, +}; + +module_i2c_driver(dps920ab_driver); + +MODULE_AUTHOR("Robert Marko <robert.marko@sartura.hr>"); +MODULE_DESCRIPTION("PMBus driver for Delta DPS920AB PSU"); +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 000000000..c7469d2cd --- /dev/null +++ b/drivers/hwmon/pmbus/fsp-3y.c @@ -0,0 +1,294 @@ +// 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; + + bool vout_linear_11; +}; + +#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_REAL; + 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) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct fsp3y_data *data = to_fsp3y_data(info); + int rv; + + /* + * Inject an exponent for non-compliant YH5151-E. + */ + if (data->vout_linear_11 && reg == PMBUS_VOUT_MODE) + return 0x1A; + + 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) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct fsp3y_data *data = to_fsp3y_data(info); + 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; + + rv = i2c_smbus_read_word_data(client, reg); + if (rv < 0) + return rv; + + /* + * Handle YH-5151E non-compliant linear11 vout voltage. + */ + if (data->vout_linear_11 && reg == PMBUS_READ_VOUT) + rv = sign_extend32(rv, 10) & 0xffff; + + return rv; +} + +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, + .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]; + + /* + * YH-5151E sometimes reports vout in linear11 and sometimes in + * linear16. This depends on the exact individual piece of hardware. One + * YH-5151E can use linear16 and another might use linear11 instead. + * + * The format can be recognized by reading VOUT_MODE - if it doesn't + * report a valid exponent, then vout uses linear11. Otherwise, the + * device is compliant and uses linear16. + */ + data->vout_linear_11 = false; + if (data->chip == yh5151e) { + rv = i2c_smbus_read_byte_data(client, PMBUS_VOUT_MODE); + if (rv < 0) + return rv; + + if (rv == 0xFF) + data->vout_linear_11 = true; + } + + 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 new file mode 100644 index 000000000..e3294a1a5 --- /dev/null +++ b/drivers/hwmon/pmbus/ibm-cffps.c @@ -0,0 +1,653 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2017 IBM Corp. + */ + +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/debugfs.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/i2c.h> +#include <linux/jiffies.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of_device.h> +#include <linux/pmbus.h> + +#include "pmbus.h" + +#define CFFPS_MFG_ID_CMD 0x99 +#define CFFPS_FRU_CMD 0x9A +#define CFFPS_PN_CMD 0x9B +#define CFFPS_HEADER_CMD 0x9C +#define CFFPS_SN_CMD 0x9E +#define CFFPS_MAX_POWER_OUT_CMD 0xA7 +#define CFFPS_CCIN_CMD 0xBD +#define CFFPS_FW_CMD 0xFA +#define CFFPS1_FW_NUM_BYTES 4 +#define CFFPS2_FW_NUM_WORDS 3 +#define CFFPS_SYS_CONFIG_CMD 0xDA +#define CFFPS_12VCS_VOUT_CMD 0xDE + +#define CFFPS_INPUT_HISTORY_CMD 0xD6 +#define CFFPS_INPUT_HISTORY_SIZE 100 + +#define CFFPS_CCIN_REVISION GENMASK(7, 0) +#define CFFPS_CCIN_REVISION_LEGACY 0xde +#define CFFPS_CCIN_VERSION GENMASK(15, 8) +#define CFFPS_CCIN_VERSION_1 0x2b +#define CFFPS_CCIN_VERSION_2 0x2e +#define CFFPS_CCIN_VERSION_3 0x51 + +/* STATUS_MFR_SPECIFIC bits */ +#define CFFPS_MFR_FAN_FAULT BIT(0) +#define CFFPS_MFR_THERMAL_FAULT BIT(1) +#define CFFPS_MFR_OV_FAULT BIT(2) +#define CFFPS_MFR_UV_FAULT BIT(3) +#define CFFPS_MFR_PS_KILL BIT(4) +#define CFFPS_MFR_OC_FAULT BIT(5) +#define CFFPS_MFR_VAUX_FAULT BIT(6) +#define CFFPS_MFR_CURRENT_SHARE_WARNING BIT(7) + +#define CFFPS_LED_BLINK (BIT(0) | BIT(6)) +#define CFFPS_LED_ON (BIT(1) | BIT(6)) +#define CFFPS_LED_OFF (BIT(2) | BIT(6)) +#define CFFPS_BLINK_RATE_MS 250 + +enum { + CFFPS_DEBUGFS_INPUT_HISTORY = 0, + CFFPS_DEBUGFS_MFG_ID, + CFFPS_DEBUGFS_FRU, + CFFPS_DEBUGFS_PN, + CFFPS_DEBUGFS_HEADER, + CFFPS_DEBUGFS_SN, + CFFPS_DEBUGFS_MAX_POWER_OUT, + CFFPS_DEBUGFS_CCIN, + CFFPS_DEBUGFS_FW, + CFFPS_DEBUGFS_ON_OFF_CONFIG, + CFFPS_DEBUGFS_NUM_ENTRIES +}; + +enum versions { cffps1, cffps2, cffps_unknown }; + +struct ibm_cffps_input_history { + struct mutex update_lock; + unsigned long last_update; + + u8 byte_count; + u8 data[CFFPS_INPUT_HISTORY_SIZE]; +}; + +struct ibm_cffps { + enum versions version; + struct i2c_client *client; + + struct ibm_cffps_input_history input_history; + + int debugfs_entries[CFFPS_DEBUGFS_NUM_ENTRIES]; + + char led_name[32]; + u8 led_state; + struct led_classdev led; +}; + +static const struct i2c_device_id ibm_cffps_id[]; + +#define to_psu(x, y) container_of((x), struct ibm_cffps, debugfs_entries[(y)]) + +static ssize_t ibm_cffps_read_input_history(struct ibm_cffps *psu, + char __user *buf, size_t count, + loff_t *ppos) +{ + int rc; + u8 msgbuf0[1] = { CFFPS_INPUT_HISTORY_CMD }; + u8 msgbuf1[CFFPS_INPUT_HISTORY_SIZE + 1] = { 0 }; + struct i2c_msg msg[2] = { + { + .addr = psu->client->addr, + .flags = psu->client->flags, + .len = 1, + .buf = msgbuf0, + }, { + .addr = psu->client->addr, + .flags = psu->client->flags | I2C_M_RD, + .len = CFFPS_INPUT_HISTORY_SIZE + 1, + .buf = msgbuf1, + }, + }; + + if (!*ppos) { + mutex_lock(&psu->input_history.update_lock); + if (time_after(jiffies, psu->input_history.last_update + HZ)) { + /* + * Use a raw i2c transfer, since we need more bytes + * than Linux I2C supports through smbus xfr (only 32). + */ + rc = i2c_transfer(psu->client->adapter, msg, 2); + if (rc < 0) { + mutex_unlock(&psu->input_history.update_lock); + return rc; + } + + psu->input_history.byte_count = msgbuf1[0]; + memcpy(psu->input_history.data, &msgbuf1[1], + CFFPS_INPUT_HISTORY_SIZE); + psu->input_history.last_update = jiffies; + } + + mutex_unlock(&psu->input_history.update_lock); + } + + return simple_read_from_buffer(buf, count, ppos, + psu->input_history.data, + psu->input_history.byte_count); +} + +static ssize_t ibm_cffps_debugfs_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + u8 cmd; + int i, rc; + int *idxp = file->private_data; + int idx = *idxp; + struct ibm_cffps *psu = to_psu(idxp, idx); + char data[I2C_SMBUS_BLOCK_MAX + 2] = { 0 }; + + pmbus_set_page(psu->client, 0, 0xff); + + switch (idx) { + case CFFPS_DEBUGFS_INPUT_HISTORY: + return ibm_cffps_read_input_history(psu, buf, count, ppos); + case CFFPS_DEBUGFS_MFG_ID: + cmd = CFFPS_MFG_ID_CMD; + break; + case CFFPS_DEBUGFS_FRU: + cmd = CFFPS_FRU_CMD; + break; + case CFFPS_DEBUGFS_PN: + cmd = CFFPS_PN_CMD; + break; + case CFFPS_DEBUGFS_HEADER: + cmd = CFFPS_HEADER_CMD; + break; + case CFFPS_DEBUGFS_SN: + cmd = CFFPS_SN_CMD; + break; + case CFFPS_DEBUGFS_MAX_POWER_OUT: + if (psu->version == cffps1) { + rc = i2c_smbus_read_word_swapped(psu->client, + CFFPS_MAX_POWER_OUT_CMD); + } else { + rc = i2c_smbus_read_word_data(psu->client, + CFFPS_MAX_POWER_OUT_CMD); + } + + if (rc < 0) + return rc; + + rc = snprintf(data, I2C_SMBUS_BLOCK_MAX, "%d", rc); + goto done; + case CFFPS_DEBUGFS_CCIN: + rc = i2c_smbus_read_word_swapped(psu->client, CFFPS_CCIN_CMD); + if (rc < 0) + return rc; + + rc = snprintf(data, 5, "%04X", rc); + goto done; + case CFFPS_DEBUGFS_FW: + switch (psu->version) { + case cffps1: + for (i = 0; i < CFFPS1_FW_NUM_BYTES; ++i) { + rc = i2c_smbus_read_byte_data(psu->client, + CFFPS_FW_CMD + + i); + if (rc < 0) + return rc; + + snprintf(&data[i * 2], 3, "%02X", rc); + } + + rc = i * 2; + break; + case cffps2: + for (i = 0; i < CFFPS2_FW_NUM_WORDS; ++i) { + rc = i2c_smbus_read_word_data(psu->client, + CFFPS_FW_CMD + + i); + if (rc < 0) + return rc; + + snprintf(&data[i * 4], 5, "%04X", rc); + } + + rc = i * 4; + break; + default: + return -EOPNOTSUPP; + } + goto done; + case CFFPS_DEBUGFS_ON_OFF_CONFIG: + rc = i2c_smbus_read_byte_data(psu->client, + PMBUS_ON_OFF_CONFIG); + if (rc < 0) + return rc; + + rc = snprintf(data, 3, "%02x", rc); + goto done; + default: + return -EINVAL; + } + + rc = i2c_smbus_read_block_data(psu->client, cmd, data); + if (rc < 0) + return rc; + +done: + data[rc] = '\n'; + rc += 2; + + return simple_read_from_buffer(buf, count, ppos, data, rc); +} + +static ssize_t ibm_cffps_debugfs_write(struct file *file, + const char __user *buf, size_t count, + loff_t *ppos) +{ + u8 data; + ssize_t rc; + int *idxp = file->private_data; + int idx = *idxp; + struct ibm_cffps *psu = to_psu(idxp, idx); + + switch (idx) { + case CFFPS_DEBUGFS_ON_OFF_CONFIG: + pmbus_set_page(psu->client, 0, 0xff); + + rc = simple_write_to_buffer(&data, 1, ppos, buf, count); + if (rc <= 0) + return rc; + + rc = i2c_smbus_write_byte_data(psu->client, + PMBUS_ON_OFF_CONFIG, data); + if (rc) + return rc; + + rc = 1; + break; + default: + return -EINVAL; + } + + return rc; +} + +static const struct file_operations ibm_cffps_fops = { + .llseek = noop_llseek, + .read = ibm_cffps_debugfs_read, + .write = ibm_cffps_debugfs_write, + .open = simple_open, +}; + +static int ibm_cffps_read_byte_data(struct i2c_client *client, int page, + int reg) +{ + int rc, mfr; + + switch (reg) { + case PMBUS_STATUS_VOUT: + case PMBUS_STATUS_IOUT: + case PMBUS_STATUS_TEMPERATURE: + case PMBUS_STATUS_FAN_12: + rc = pmbus_read_byte_data(client, page, reg); + if (rc < 0) + return rc; + + mfr = pmbus_read_byte_data(client, page, + PMBUS_STATUS_MFR_SPECIFIC); + if (mfr < 0) + /* + * Return the status register instead of an error, + * since we successfully read status. + */ + return rc; + + /* Add MFR_SPECIFIC bits to the standard pmbus status regs. */ + if (reg == PMBUS_STATUS_FAN_12) { + if (mfr & CFFPS_MFR_FAN_FAULT) + rc |= PB_FAN_FAN1_FAULT; + } else if (reg == PMBUS_STATUS_TEMPERATURE) { + if (mfr & CFFPS_MFR_THERMAL_FAULT) + rc |= PB_TEMP_OT_FAULT; + } else if (reg == PMBUS_STATUS_VOUT) { + if (mfr & (CFFPS_MFR_OV_FAULT | CFFPS_MFR_VAUX_FAULT)) + rc |= PB_VOLTAGE_OV_FAULT; + if (mfr & CFFPS_MFR_UV_FAULT) + rc |= PB_VOLTAGE_UV_FAULT; + } else if (reg == PMBUS_STATUS_IOUT) { + if (mfr & CFFPS_MFR_OC_FAULT) + rc |= PB_IOUT_OC_FAULT; + if (mfr & CFFPS_MFR_CURRENT_SHARE_WARNING) + rc |= PB_CURRENT_SHARE_FAULT; + } + break; + default: + rc = -ENODATA; + break; + } + + return rc; +} + +static int ibm_cffps_read_word_data(struct i2c_client *client, int page, + int phase, int reg) +{ + int rc, mfr; + + switch (reg) { + case PMBUS_STATUS_WORD: + rc = pmbus_read_word_data(client, page, phase, reg); + if (rc < 0) + return rc; + + mfr = pmbus_read_byte_data(client, page, + PMBUS_STATUS_MFR_SPECIFIC); + if (mfr < 0) + /* + * Return the status register instead of an error, + * since we successfully read status. + */ + return rc; + + if (mfr & CFFPS_MFR_PS_KILL) + rc |= PB_STATUS_OFF; + break; + case PMBUS_VIRT_READ_VMON: + rc = pmbus_read_word_data(client, page, phase, + CFFPS_12VCS_VOUT_CMD); + break; + default: + rc = -ENODATA; + break; + } + + return rc; +} + +static int ibm_cffps_led_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + int rc; + u8 next_led_state; + struct ibm_cffps *psu = container_of(led_cdev, struct ibm_cffps, led); + + if (brightness == LED_OFF) { + next_led_state = CFFPS_LED_OFF; + } else { + brightness = LED_FULL; + + if (psu->led_state != CFFPS_LED_BLINK) + next_led_state = CFFPS_LED_ON; + else + next_led_state = CFFPS_LED_BLINK; + } + + dev_dbg(&psu->client->dev, "LED brightness set: %d. Command: %d.\n", + brightness, next_led_state); + + pmbus_set_page(psu->client, 0, 0xff); + + rc = i2c_smbus_write_byte_data(psu->client, CFFPS_SYS_CONFIG_CMD, + next_led_state); + if (rc < 0) + return rc; + + psu->led_state = next_led_state; + led_cdev->brightness = brightness; + + return 0; +} + +static int ibm_cffps_led_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + int rc; + struct ibm_cffps *psu = container_of(led_cdev, struct ibm_cffps, led); + + dev_dbg(&psu->client->dev, "LED blink set.\n"); + + pmbus_set_page(psu->client, 0, 0xff); + + rc = i2c_smbus_write_byte_data(psu->client, CFFPS_SYS_CONFIG_CMD, + CFFPS_LED_BLINK); + if (rc < 0) + return rc; + + psu->led_state = CFFPS_LED_BLINK; + led_cdev->brightness = LED_FULL; + *delay_on = CFFPS_BLINK_RATE_MS; + *delay_off = CFFPS_BLINK_RATE_MS; + + return 0; +} + +static void ibm_cffps_create_led_class(struct ibm_cffps *psu) +{ + int rc; + struct i2c_client *client = psu->client; + struct device *dev = &client->dev; + + snprintf(psu->led_name, sizeof(psu->led_name), "%s-%02x", client->name, + client->addr); + psu->led.name = psu->led_name; + psu->led.max_brightness = LED_FULL; + psu->led.brightness_set_blocking = ibm_cffps_led_brightness_set; + psu->led.blink_set = ibm_cffps_led_blink_set; + + rc = devm_led_classdev_register(dev, &psu->led); + if (rc) + dev_warn(dev, "failed to register led class: %d\n", rc); + else + i2c_smbus_write_byte_data(client, CFFPS_SYS_CONFIG_CMD, + CFFPS_LED_OFF); +} + +static struct pmbus_driver_info ibm_cffps_info[] = { + [cffps1] = { + .pages = 1, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | + PMBUS_HAVE_PIN | PMBUS_HAVE_FAN12 | PMBUS_HAVE_TEMP | + PMBUS_HAVE_TEMP2 | PMBUS_HAVE_TEMP3 | + PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_STATUS_FAN12, + .read_byte_data = ibm_cffps_read_byte_data, + .read_word_data = ibm_cffps_read_word_data, + }, + [cffps2] = { + .pages = 2, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | + PMBUS_HAVE_PIN | PMBUS_HAVE_FAN12 | PMBUS_HAVE_TEMP | + PMBUS_HAVE_TEMP2 | PMBUS_HAVE_TEMP3 | + PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_STATUS_FAN12 | PMBUS_HAVE_VMON, + .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_TEMP3 | + PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT, + .read_byte_data = ibm_cffps_read_byte_data, + .read_word_data = ibm_cffps_read_word_data, + }, +}; + +static struct pmbus_platform_data ibm_cffps_pdata = { + .flags = PMBUS_SKIP_STATUS_CHECK | PMBUS_NO_CAPABILITY, +}; + +static int ibm_cffps_probe(struct i2c_client *client) +{ + int i, rc; + enum versions vs = cffps_unknown; + struct dentry *debugfs; + struct dentry *ibm_cffps_dir; + struct ibm_cffps *psu; + const void *md = of_device_get_match_data(&client->dev); + const struct i2c_device_id *id; + + if (md) { + vs = (enum versions)md; + } else { + id = i2c_match_id(ibm_cffps_id, client); + if (id) + vs = (enum versions)id->driver_data; + } + + if (vs == cffps_unknown) { + u16 ccin_revision = 0; + u16 ccin_version = CFFPS_CCIN_VERSION_1; + int ccin = i2c_smbus_read_word_swapped(client, CFFPS_CCIN_CMD); + char mfg_id[I2C_SMBUS_BLOCK_MAX + 2] = { 0 }; + + if (ccin > 0) { + ccin_revision = FIELD_GET(CFFPS_CCIN_REVISION, ccin); + ccin_version = FIELD_GET(CFFPS_CCIN_VERSION, ccin); + } + + rc = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, mfg_id); + if (rc < 0) { + dev_err(&client->dev, "Failed to read Manufacturer ID\n"); + return rc; + } + + switch (ccin_version) { + default: + case CFFPS_CCIN_VERSION_1: + if ((strncmp(mfg_id, "ACBE", 4) == 0) || + (strncmp(mfg_id, "ARTE", 4) == 0)) + vs = cffps1; + else + vs = cffps2; + break; + case CFFPS_CCIN_VERSION_2: + vs = cffps2; + break; + case CFFPS_CCIN_VERSION_3: + if (ccin_revision == CFFPS_CCIN_REVISION_LEGACY) + vs = cffps1; + else + vs = cffps2; + break; + } + + /* Set the client name to include the version number. */ + snprintf(client->name, I2C_NAME_SIZE, "cffps%d", vs + 1); + } + + client->dev.platform_data = &ibm_cffps_pdata; + rc = pmbus_do_probe(client, &ibm_cffps_info[vs]); + if (rc) + return rc; + + /* + * Don't fail the probe if there isn't enough memory for leds and + * debugfs. + */ + psu = devm_kzalloc(&client->dev, sizeof(*psu), GFP_KERNEL); + if (!psu) + return 0; + + psu->version = vs; + psu->client = client; + mutex_init(&psu->input_history.update_lock); + psu->input_history.last_update = jiffies - HZ; + + ibm_cffps_create_led_class(psu); + + /* Don't fail the probe if we can't create debugfs */ + debugfs = pmbus_get_debugfs_dir(client); + if (!debugfs) + return 0; + + ibm_cffps_dir = debugfs_create_dir(client->name, debugfs); + if (!ibm_cffps_dir) + return 0; + + for (i = 0; i < CFFPS_DEBUGFS_NUM_ENTRIES; ++i) + psu->debugfs_entries[i] = i; + + debugfs_create_file("input_history", 0444, ibm_cffps_dir, + &psu->debugfs_entries[CFFPS_DEBUGFS_INPUT_HISTORY], + &ibm_cffps_fops); + debugfs_create_file("mfg_id", 0444, ibm_cffps_dir, + &psu->debugfs_entries[CFFPS_DEBUGFS_MFG_ID], + &ibm_cffps_fops); + debugfs_create_file("fru", 0444, ibm_cffps_dir, + &psu->debugfs_entries[CFFPS_DEBUGFS_FRU], + &ibm_cffps_fops); + debugfs_create_file("part_number", 0444, ibm_cffps_dir, + &psu->debugfs_entries[CFFPS_DEBUGFS_PN], + &ibm_cffps_fops); + debugfs_create_file("header", 0444, ibm_cffps_dir, + &psu->debugfs_entries[CFFPS_DEBUGFS_HEADER], + &ibm_cffps_fops); + debugfs_create_file("serial_number", 0444, ibm_cffps_dir, + &psu->debugfs_entries[CFFPS_DEBUGFS_SN], + &ibm_cffps_fops); + debugfs_create_file("max_power_out", 0444, ibm_cffps_dir, + &psu->debugfs_entries[CFFPS_DEBUGFS_MAX_POWER_OUT], + &ibm_cffps_fops); + debugfs_create_file("ccin", 0444, ibm_cffps_dir, + &psu->debugfs_entries[CFFPS_DEBUGFS_CCIN], + &ibm_cffps_fops); + debugfs_create_file("fw_version", 0444, ibm_cffps_dir, + &psu->debugfs_entries[CFFPS_DEBUGFS_FW], + &ibm_cffps_fops); + debugfs_create_file("on_off_config", 0644, ibm_cffps_dir, + &psu->debugfs_entries[CFFPS_DEBUGFS_ON_OFF_CONFIG], + &ibm_cffps_fops); + + return 0; +} + +static const struct i2c_device_id ibm_cffps_id[] = { + { "ibm_cffps1", cffps1 }, + { "ibm_cffps2", cffps2 }, + { "ibm_cffps", cffps_unknown }, + {} +}; +MODULE_DEVICE_TABLE(i2c, ibm_cffps_id); + +static const struct of_device_id ibm_cffps_of_match[] = { + { + .compatible = "ibm,cffps1", + .data = (void *)cffps1 + }, + { + .compatible = "ibm,cffps2", + .data = (void *)cffps2 + }, + { + .compatible = "ibm,cffps", + .data = (void *)cffps_unknown + }, + {} +}; +MODULE_DEVICE_TABLE(of, ibm_cffps_of_match); + +static struct i2c_driver ibm_cffps_driver = { + .driver = { + .name = "ibm-cffps", + .of_match_table = ibm_cffps_of_match, + }, + .probe_new = ibm_cffps_probe, + .id_table = ibm_cffps_id, +}; + +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 new file mode 100644 index 000000000..0f614e8d9 --- /dev/null +++ b/drivers/hwmon/pmbus/inspur-ipsps.c @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2019 Inspur Corp. + */ + +#include <linux/debugfs.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pmbus.h> +#include <linux/hwmon-sysfs.h> + +#include "pmbus.h" + +#define IPSPS_REG_VENDOR_ID 0x99 +#define IPSPS_REG_MODEL 0x9A +#define IPSPS_REG_FW_VERSION 0x9B +#define IPSPS_REG_PN 0x9C +#define IPSPS_REG_SN 0x9E +#define IPSPS_REG_HW_VERSION 0xB0 +#define IPSPS_REG_MODE 0xFC + +#define MODE_ACTIVE 0x55 +#define MODE_STANDBY 0x0E +#define MODE_REDUNDANCY 0x00 + +#define MODE_ACTIVE_STRING "active" +#define MODE_STANDBY_STRING "standby" +#define MODE_REDUNDANCY_STRING "redundancy" + +enum ipsps_index { + vendor, + model, + fw_version, + part_number, + serial_number, + hw_version, + mode, + num_regs, +}; + +static const u8 ipsps_regs[num_regs] = { + [vendor] = IPSPS_REG_VENDOR_ID, + [model] = IPSPS_REG_MODEL, + [fw_version] = IPSPS_REG_FW_VERSION, + [part_number] = IPSPS_REG_PN, + [serial_number] = IPSPS_REG_SN, + [hw_version] = IPSPS_REG_HW_VERSION, + [mode] = IPSPS_REG_MODE, +}; + +static ssize_t ipsps_string_show(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + u8 reg; + int rc; + char *p; + char data[I2C_SMBUS_BLOCK_MAX + 1]; + struct i2c_client *client = to_i2c_client(dev->parent); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + + reg = ipsps_regs[attr->index]; + rc = i2c_smbus_read_block_data(client, reg, data); + if (rc < 0) + return rc; + + /* filled with printable characters, ending with # */ + p = memscan(data, '#', rc); + *p = '\0'; + + return sysfs_emit(buf, "%s\n", data); +} + +static ssize_t ipsps_fw_version_show(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + u8 reg; + int rc; + u8 data[I2C_SMBUS_BLOCK_MAX] = { 0 }; + struct i2c_client *client = to_i2c_client(dev->parent); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + + reg = ipsps_regs[attr->index]; + rc = i2c_smbus_read_block_data(client, reg, data); + if (rc < 0) + return rc; + + if (rc != 6) + return -EPROTO; + + 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, + struct device_attribute *devattr, char *buf) +{ + u8 reg; + int rc; + struct i2c_client *client = to_i2c_client(dev->parent); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + + reg = ipsps_regs[attr->index]; + rc = i2c_smbus_read_byte_data(client, reg); + if (rc < 0) + return rc; + + switch (rc) { + case MODE_ACTIVE: + return sysfs_emit(buf, "[%s] %s %s\n", + MODE_ACTIVE_STRING, + MODE_STANDBY_STRING, MODE_REDUNDANCY_STRING); + case MODE_STANDBY: + return sysfs_emit(buf, "%s [%s] %s\n", + MODE_ACTIVE_STRING, + MODE_STANDBY_STRING, MODE_REDUNDANCY_STRING); + case MODE_REDUNDANCY: + return sysfs_emit(buf, "%s %s [%s]\n", + MODE_ACTIVE_STRING, + MODE_STANDBY_STRING, MODE_REDUNDANCY_STRING); + default: + return sysfs_emit(buf, "unspecified\n"); + } +} + +static ssize_t ipsps_mode_store(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + u8 reg; + int rc; + struct i2c_client *client = to_i2c_client(dev->parent); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + + reg = ipsps_regs[attr->index]; + if (sysfs_streq(MODE_STANDBY_STRING, buf)) { + rc = i2c_smbus_write_byte_data(client, reg, + MODE_STANDBY); + if (rc < 0) + return rc; + return count; + } else if (sysfs_streq(MODE_ACTIVE_STRING, buf)) { + rc = i2c_smbus_write_byte_data(client, reg, + MODE_ACTIVE); + if (rc < 0) + return rc; + return count; + } + + return -EINVAL; +} + +static SENSOR_DEVICE_ATTR_RO(vendor, ipsps_string, vendor); +static SENSOR_DEVICE_ATTR_RO(model, ipsps_string, model); +static SENSOR_DEVICE_ATTR_RO(part_number, ipsps_string, part_number); +static SENSOR_DEVICE_ATTR_RO(serial_number, ipsps_string, serial_number); +static SENSOR_DEVICE_ATTR_RO(hw_version, ipsps_string, hw_version); +static SENSOR_DEVICE_ATTR_RO(fw_version, ipsps_fw_version, fw_version); +static SENSOR_DEVICE_ATTR_RW(mode, ipsps_mode, mode); + +static struct attribute *ipsps_attrs[] = { + &sensor_dev_attr_vendor.dev_attr.attr, + &sensor_dev_attr_model.dev_attr.attr, + &sensor_dev_attr_part_number.dev_attr.attr, + &sensor_dev_attr_serial_number.dev_attr.attr, + &sensor_dev_attr_hw_version.dev_attr.attr, + &sensor_dev_attr_fw_version.dev_attr.attr, + &sensor_dev_attr_mode.dev_attr.attr, + NULL, +}; + +ATTRIBUTE_GROUPS(ipsps); + +static struct pmbus_driver_info ipsps_info = { + .pages = 1, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | + PMBUS_HAVE_IIN | PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | + PMBUS_HAVE_FAN12 | PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | + PMBUS_HAVE_TEMP3 | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_INPUT | + PMBUS_HAVE_STATUS_TEMP | PMBUS_HAVE_STATUS_FAN12, + .groups = ipsps_groups, +}; + +static struct pmbus_platform_data ipsps_pdata = { + .flags = PMBUS_SKIP_STATUS_CHECK, +}; + +static int ipsps_probe(struct i2c_client *client) +{ + client->dev.platform_data = &ipsps_pdata; + return pmbus_do_probe(client, &ipsps_info); +} + +static const struct i2c_device_id ipsps_id[] = { + { "ipsps1", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, ipsps_id); + +#ifdef CONFIG_OF +static const struct of_device_id ipsps_of_match[] = { + { .compatible = "inspur,ipsps1" }, + {} +}; +MODULE_DEVICE_TABLE(of, ipsps_of_match); +#endif + +static struct i2c_driver ipsps_driver = { + .driver = { + .name = "inspur-ipsps", + .of_match_table = of_match_ptr(ipsps_of_match), + }, + .probe_new = ipsps_probe, + .id_table = ipsps_id, +}; + +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 new file mode 100644 index 000000000..a6cf98e49 --- /dev/null +++ b/drivers/hwmon/pmbus/ir35221.c @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for IR35221 + * + * Copyright (C) IBM Corporation 2017. + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include "pmbus.h" + +#define IR35221_MFR_VIN_PEAK 0xc5 +#define IR35221_MFR_VOUT_PEAK 0xc6 +#define IR35221_MFR_IOUT_PEAK 0xc7 +#define IR35221_MFR_TEMP_PEAK 0xc8 +#define IR35221_MFR_VIN_VALLEY 0xc9 +#define IR35221_MFR_VOUT_VALLEY 0xca +#define IR35221_MFR_IOUT_VALLEY 0xcb +#define IR35221_MFR_TEMP_VALLEY 0xcc + +static int ir35221_read_word_data(struct i2c_client *client, int page, + int phase, int reg) +{ + int ret; + + switch (reg) { + case PMBUS_VIRT_READ_VIN_MAX: + ret = pmbus_read_word_data(client, page, phase, + IR35221_MFR_VIN_PEAK); + break; + case PMBUS_VIRT_READ_VOUT_MAX: + ret = pmbus_read_word_data(client, page, phase, + IR35221_MFR_VOUT_PEAK); + break; + case PMBUS_VIRT_READ_IOUT_MAX: + ret = pmbus_read_word_data(client, page, phase, + IR35221_MFR_IOUT_PEAK); + break; + case PMBUS_VIRT_READ_TEMP_MAX: + ret = pmbus_read_word_data(client, page, phase, + IR35221_MFR_TEMP_PEAK); + break; + case PMBUS_VIRT_READ_VIN_MIN: + ret = pmbus_read_word_data(client, page, phase, + IR35221_MFR_VIN_VALLEY); + break; + case PMBUS_VIRT_READ_VOUT_MIN: + ret = pmbus_read_word_data(client, page, phase, + IR35221_MFR_VOUT_VALLEY); + break; + case PMBUS_VIRT_READ_IOUT_MIN: + ret = pmbus_read_word_data(client, page, phase, + IR35221_MFR_IOUT_VALLEY); + break; + case PMBUS_VIRT_READ_TEMP_MIN: + ret = pmbus_read_word_data(client, page, phase, + IR35221_MFR_TEMP_VALLEY); + break; + default: + ret = -ENODATA; + break; + } + + return ret; +} + +static int ir35221_probe(struct i2c_client *client) +{ + struct pmbus_driver_info *info; + 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_block_data(client, PMBUS_MFR_ID, buf); + if (ret < 0) { + dev_err(&client->dev, "Failed to read PMBUS_MFR_ID\n"); + return ret; + } + if (ret != 2 || strncmp(buf, "RI", strlen("RI"))) { + dev_err(&client->dev, "MFR_ID unrecognised\n"); + return -ENODEV; + } + + ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf); + if (ret < 0) { + dev_err(&client->dev, "Failed to read PMBUS_MFR_MODEL\n"); + return ret; + } + if (ret != 2 || !(buf[0] == 0x6c && buf[1] == 0x00)) { + dev_err(&client->dev, "MFR_MODEL unrecognised\n"); + return -ENODEV; + } + + info = devm_kzalloc(&client->dev, sizeof(struct pmbus_driver_info), + GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->read_word_data = ir35221_read_word_data; + + info->pages = 2; + info->format[PSC_VOLTAGE_IN] = linear; + info->format[PSC_VOLTAGE_OUT] = linear; + info->format[PSC_CURRENT_IN] = linear; + info->format[PSC_CURRENT_OUT] = linear; + info->format[PSC_POWER] = linear; + info->format[PSC_TEMPERATURE] = linear; + + info->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_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT + | PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP; + info->func[1] = info->func[0]; + + return pmbus_do_probe(client, info); +} + +static const struct i2c_device_id ir35221_id[] = { + {"ir35221", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, ir35221_id); + +static struct i2c_driver ir35221_driver = { + .driver = { + .name = "ir35221", + }, + .probe_new = ir35221_probe, + .id_table = ir35221_id, +}; + +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 000000000..4dca4767f --- /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 new file mode 100644 index 000000000..09276e397 --- /dev/null +++ b/drivers/hwmon/pmbus/ir38064.c @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Hardware monitoring driver for Infineon IR38064 + * + * Copyright (c) 2017 Google Inc + * + * VOUT_MODE is not supported by the device. The driver fakes VOUT linear16 + * mode with exponent value -8 as direct mode with m=256/b=0/R=0. + * + * The device supports VOUT_PEAK, IOUT_PEAK, and TEMPERATURE_PEAK, however + * this driver does not currently support them. + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/regulator/driver.h> +#include "pmbus.h" + +#if IS_ENABLED(CONFIG_SENSORS_IR38064_REGULATOR) +static const struct regulator_desc ir38064_reg_desc[] = { + PMBUS_REGULATOR("vout", 0), +}; +#endif /* CONFIG_SENSORS_IR38064_REGULATOR */ + +static struct pmbus_driver_info ir38064_info = { + .pages = 1, + .format[PSC_VOLTAGE_IN] = linear, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_CURRENT_OUT] = linear, + .format[PSC_POWER] = linear, + .format[PSC_TEMPERATURE] = linear, + .m[PSC_VOLTAGE_OUT] = 256, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 0, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP + | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT + | PMBUS_HAVE_POUT, +#if IS_ENABLED(CONFIG_SENSORS_IR38064_REGULATOR) + .num_regulators = 1, + .reg_desc = ir38064_reg_desc, +#endif +}; + +static int ir38064_probe(struct i2c_client *client) +{ + return pmbus_do_probe(client, &ir38064_info); +} + +static const struct i2c_device_id ir38064_id[] = { + {"ir38060", 0}, + {"ir38064", 0}, + {"ir38164", 0}, + {"ir38263", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, ir38064_id); + +static const struct of_device_id __maybe_unused ir38064_of_match[] = { + { .compatible = "infineon,ir38060" }, + { .compatible = "infineon,ir38064" }, + { .compatible = "infineon,ir38164" }, + { .compatible = "infineon,ir38263" }, + {} +}; + +MODULE_DEVICE_TABLE(of, ir38064_of_match); + +/* This is the driver that will be inserted */ +static struct i2c_driver ir38064_driver = { + .driver = { + .name = "ir38064", + .of_match_table = of_match_ptr(ir38064_of_match), + }, + .probe_new = ir38064_probe, + .id_table = ir38064_id, +}; + +module_i2c_driver(ir38064_driver); + +MODULE_AUTHOR("Maxim Sloyko <maxims@google.com>"); +MODULE_DESCRIPTION("PMBus driver for Infineon IR38064 and compatible chips"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/irps5401.c b/drivers/hwmon/pmbus/irps5401.c new file mode 100644 index 000000000..de3449e4d --- /dev/null +++ b/drivers/hwmon/pmbus/irps5401.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Hardware monitoring driver for the Infineon IRPS5401M PMIC. + * + * Copyright (c) 2019 SED Systems, a division of Calian Ltd. + * + * The device supports VOUT_PEAK, IOUT_PEAK, and TEMPERATURE_PEAK, however + * this driver does not currently support them. + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include "pmbus.h" + +#define IRPS5401_SW_FUNC (PMBUS_HAVE_VIN | PMBUS_HAVE_IIN | \ + PMBUS_HAVE_STATUS_INPUT | \ + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | \ + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | \ + PMBUS_HAVE_PIN | PMBUS_HAVE_POUT | \ + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP) + +#define IRPS5401_LDO_FUNC (PMBUS_HAVE_VIN | \ + PMBUS_HAVE_STATUS_INPUT | \ + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | \ + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | \ + PMBUS_HAVE_PIN | PMBUS_HAVE_POUT | \ + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP) + +static struct pmbus_driver_info irps5401_info = { + .pages = 5, + .func[0] = IRPS5401_SW_FUNC, + .func[1] = IRPS5401_SW_FUNC, + .func[2] = IRPS5401_SW_FUNC, + .func[3] = IRPS5401_SW_FUNC, + .func[4] = IRPS5401_LDO_FUNC, +}; + +static int irps5401_probe(struct i2c_client *client) +{ + return pmbus_do_probe(client, &irps5401_info); +} + +static const struct i2c_device_id irps5401_id[] = { + {"irps5401", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, irps5401_id); + +static struct i2c_driver irps5401_driver = { + .driver = { + .name = "irps5401", + }, + .probe_new = irps5401_probe, + .id_table = irps5401_id, +}; + +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 new file mode 100644 index 000000000..1a8caff1a --- /dev/null +++ b/drivers/hwmon/pmbus/isl68137.c @@ -0,0 +1,335 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Hardware monitoring driver for Renesas Digital Multiphase Voltage Regulators + * + * Copyright (c) 2017 Google Inc + * Copyright (c) 2020 Renesas Electronics America + * + */ + +#include <linux/err.h> +#include <linux/hwmon-sysfs.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/sysfs.h> + +#include "pmbus.h" + +#define ISL68137_VOUT_AVS 0x30 +#define RAA_DMPVR2_READ_VMON 0xc8 + +enum chips { + isl68137, + isl68220, + isl68221, + isl68222, + isl68223, + isl68224, + isl68225, + isl68226, + isl68227, + isl68229, + isl68233, + isl68239, + isl69222, + isl69223, + isl69224, + isl69225, + isl69227, + isl69228, + isl69234, + isl69236, + isl69239, + isl69242, + isl69243, + isl69247, + isl69248, + isl69254, + isl69255, + isl69256, + isl69259, + isl69260, + isl69268, + isl69269, + isl69298, + raa228000, + raa228004, + raa228006, + raa228228, + raa229001, + raa229004, +}; + +enum variants { + raa_dmpvr1_2rail, + raa_dmpvr2_1rail, + raa_dmpvr2_2rail, + raa_dmpvr2_2rail_nontc, + raa_dmpvr2_3rail, + raa_dmpvr2_hv, +}; + +static const struct i2c_device_id raa_dmpvr_id[]; + +static ssize_t isl68137_avs_enable_show_page(struct i2c_client *client, + int page, + char *buf) +{ + int val = pmbus_read_byte_data(client, page, PMBUS_OPERATION); + + return sprintf(buf, "%d\n", + (val & ISL68137_VOUT_AVS) == ISL68137_VOUT_AVS ? 1 : 0); +} + +static ssize_t isl68137_avs_enable_store_page(struct i2c_client *client, + int page, + const char *buf, size_t count) +{ + int rc, op_val; + bool result; + + rc = kstrtobool(buf, &result); + if (rc) + return rc; + + op_val = result ? ISL68137_VOUT_AVS : 0; + + /* + * Writes to VOUT setpoint over AVSBus will persist after the VRM is + * switched to PMBus control. Switching back to AVSBus control + * restores this persisted setpoint rather than re-initializing to + * PMBus VOUT_COMMAND. Writing VOUT_COMMAND first over PMBus before + * enabling AVS control is the workaround. + */ + if (op_val == ISL68137_VOUT_AVS) { + rc = pmbus_read_word_data(client, page, 0xff, + PMBUS_VOUT_COMMAND); + if (rc < 0) + return rc; + + rc = pmbus_write_word_data(client, page, PMBUS_VOUT_COMMAND, + rc); + if (rc < 0) + return rc; + } + + rc = pmbus_update_byte_data(client, page, PMBUS_OPERATION, + ISL68137_VOUT_AVS, op_val); + + return (rc < 0) ? rc : count; +} + +static ssize_t isl68137_avs_enable_show(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev->parent); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + + return isl68137_avs_enable_show_page(client, attr->index, buf); +} + +static ssize_t isl68137_avs_enable_store(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev->parent); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + + return isl68137_avs_enable_store_page(client, attr->index, buf, count); +} + +static SENSOR_DEVICE_ATTR_RW(avs0_enable, isl68137_avs_enable, 0); +static SENSOR_DEVICE_ATTR_RW(avs1_enable, isl68137_avs_enable, 1); + +static struct attribute *enable_attrs[] = { + &sensor_dev_attr_avs0_enable.dev_attr.attr, + &sensor_dev_attr_avs1_enable.dev_attr.attr, + NULL, +}; + +static const struct attribute_group enable_group = { + .attrs = enable_attrs, +}; + +static const struct attribute_group *isl68137_attribute_groups[] = { + &enable_group, + NULL, +}; + +static int raa_dmpvr2_read_word_data(struct i2c_client *client, int page, + int phase, int reg) +{ + int ret; + + switch (reg) { + case PMBUS_VIRT_READ_VMON: + ret = pmbus_read_word_data(client, page, phase, + RAA_DMPVR2_READ_VMON); + break; + default: + ret = -ENODATA; + break; + } + + return ret; +} + +static struct pmbus_driver_info raa_dmpvr_info = { + .pages = 3, + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_CURRENT_IN] = direct, + .format[PSC_CURRENT_OUT] = direct, + .format[PSC_POWER] = direct, + .format[PSC_TEMPERATURE] = direct, + .m[PSC_VOLTAGE_IN] = 1, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 2, + .m[PSC_VOLTAGE_OUT] = 1, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 3, + .m[PSC_CURRENT_IN] = 1, + .b[PSC_CURRENT_IN] = 0, + .R[PSC_CURRENT_IN] = 2, + .m[PSC_CURRENT_OUT] = 1, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = 1, + .m[PSC_POWER] = 1, + .b[PSC_POWER] = 0, + .R[PSC_POWER] = 0, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 0, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN | PMBUS_HAVE_PIN + | PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 + | PMBUS_HAVE_TEMP3 | PMBUS_HAVE_STATUS_TEMP + | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_POUT + | PMBUS_HAVE_VMON, + .func[1] = PMBUS_HAVE_IIN | PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP3 | PMBUS_HAVE_STATUS_TEMP + | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_IOUT + | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_POUT, + .func[2] = PMBUS_HAVE_IIN | PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP3 | PMBUS_HAVE_STATUS_TEMP + | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_IOUT + | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_POUT, +}; + +static int isl68137_probe(struct i2c_client *client) +{ + struct pmbus_driver_info *info; + + info = devm_kzalloc(&client->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + memcpy(info, &raa_dmpvr_info, sizeof(*info)); + + switch (i2c_match_id(raa_dmpvr_id, client)->driver_data) { + case raa_dmpvr1_2rail: + info->pages = 2; + info->R[PSC_VOLTAGE_IN] = 3; + info->func[0] &= ~PMBUS_HAVE_VMON; + info->func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT + | PMBUS_HAVE_POUT; + info->groups = isl68137_attribute_groups; + break; + case raa_dmpvr2_1rail: + info->pages = 1; + info->read_word_data = raa_dmpvr2_read_word_data; + break; + case raa_dmpvr2_2rail_nontc: + info->func[0] &= ~PMBUS_HAVE_TEMP3; + info->func[1] &= ~PMBUS_HAVE_TEMP3; + fallthrough; + case raa_dmpvr2_2rail: + info->pages = 2; + info->read_word_data = raa_dmpvr2_read_word_data; + break; + case raa_dmpvr2_3rail: + info->read_word_data = raa_dmpvr2_read_word_data; + break; + case raa_dmpvr2_hv: + info->pages = 1; + info->R[PSC_VOLTAGE_IN] = 1; + info->m[PSC_VOLTAGE_OUT] = 2; + info->R[PSC_VOLTAGE_OUT] = 2; + info->m[PSC_CURRENT_IN] = 2; + info->m[PSC_POWER] = 2; + info->R[PSC_POWER] = -1; + info->read_word_data = raa_dmpvr2_read_word_data; + break; + default: + return -ENODEV; + } + + return pmbus_do_probe(client, info); +} + +static const struct i2c_device_id raa_dmpvr_id[] = { + {"isl68137", raa_dmpvr1_2rail}, + {"isl68220", raa_dmpvr2_2rail}, + {"isl68221", raa_dmpvr2_3rail}, + {"isl68222", raa_dmpvr2_2rail}, + {"isl68223", raa_dmpvr2_2rail}, + {"isl68224", raa_dmpvr2_3rail}, + {"isl68225", raa_dmpvr2_2rail}, + {"isl68226", raa_dmpvr2_3rail}, + {"isl68227", raa_dmpvr2_1rail}, + {"isl68229", raa_dmpvr2_3rail}, + {"isl68233", raa_dmpvr2_2rail}, + {"isl68239", raa_dmpvr2_3rail}, + + {"isl69222", raa_dmpvr2_2rail}, + {"isl69223", raa_dmpvr2_3rail}, + {"isl69224", raa_dmpvr2_2rail}, + {"isl69225", raa_dmpvr2_2rail}, + {"isl69227", raa_dmpvr2_3rail}, + {"isl69228", raa_dmpvr2_3rail}, + {"isl69234", raa_dmpvr2_2rail}, + {"isl69236", raa_dmpvr2_2rail}, + {"isl69239", raa_dmpvr2_3rail}, + {"isl69242", raa_dmpvr2_2rail}, + {"isl69243", raa_dmpvr2_1rail}, + {"isl69247", raa_dmpvr2_2rail}, + {"isl69248", raa_dmpvr2_2rail}, + {"isl69254", raa_dmpvr2_2rail}, + {"isl69255", raa_dmpvr2_2rail}, + {"isl69256", raa_dmpvr2_2rail}, + {"isl69259", raa_dmpvr2_2rail}, + {"isl69260", raa_dmpvr2_2rail}, + {"isl69268", raa_dmpvr2_2rail}, + {"isl69269", raa_dmpvr2_3rail}, + {"isl69298", raa_dmpvr2_2rail}, + + {"raa228000", raa_dmpvr2_hv}, + {"raa228004", raa_dmpvr2_hv}, + {"raa228006", raa_dmpvr2_hv}, + {"raa228228", raa_dmpvr2_2rail_nontc}, + {"raa229001", raa_dmpvr2_2rail}, + {"raa229004", raa_dmpvr2_2rail}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, raa_dmpvr_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver isl68137_driver = { + .driver = { + .name = "isl68137", + }, + .probe_new = isl68137_probe, + .id_table = raa_dmpvr_id, +}; + +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 new file mode 100644 index 000000000..09792cd03 --- /dev/null +++ b/drivers/hwmon/pmbus/lm25066.c @@ -0,0 +1,580 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for LM25056 / LM25066 / LM5064 / LM5066 + * + * Copyright (c) 2011 Ericsson AB. + * Copyright (c) 2013 Guenter Roeck + */ + +#include <linux/bitops.h> +#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/log2.h> +#include <linux/of_device.h> +#include "pmbus.h" + +enum chips { lm25056, lm25066, lm5064, lm5066, lm5066i }; + +#define LM25066_READ_VAUX 0xd0 +#define LM25066_MFR_READ_IIN 0xd1 +#define LM25066_MFR_READ_PIN 0xd2 +#define LM25066_MFR_IIN_OC_WARN_LIMIT 0xd3 +#define LM25066_MFR_PIN_OP_WARN_LIMIT 0xd4 +#define LM25066_READ_PIN_PEAK 0xd5 +#define LM25066_CLEAR_PIN_PEAK 0xd6 +#define LM25066_DEVICE_SETUP 0xd9 +#define LM25066_READ_AVG_VIN 0xdc +#define LM25066_SAMPLES_FOR_AVG 0xdb +#define LM25066_READ_AVG_VOUT 0xdd +#define LM25066_READ_AVG_IIN 0xde +#define LM25066_READ_AVG_PIN 0xdf + +#define LM25066_DEV_SETUP_CL BIT(4) /* Current limit */ + +#define LM25066_SAMPLES_FOR_AVG_MAX 4096 + +/* LM25056 only */ + +#define LM25056_VAUX_OV_WARN_LIMIT 0xe3 +#define LM25056_VAUX_UV_WARN_LIMIT 0xe4 + +#define LM25056_MFR_STS_VAUX_OV_WARN BIT(1) +#define LM25056_MFR_STS_VAUX_UV_WARN BIT(0) + +struct __coeff { + short m, b, R; +}; + +#define PSC_CURRENT_IN_L (PSC_NUM_CLASSES) +#define PSC_POWER_L (PSC_NUM_CLASSES + 1) + +static const struct __coeff lm25066_coeff[][PSC_NUM_CLASSES + 2] = { + [lm25056] = { + [PSC_VOLTAGE_IN] = { + .m = 16296, + .b = 1343, + .R = -2, + }, + [PSC_CURRENT_IN] = { + .m = 13797, + .b = -1833, + .R = -2, + }, + [PSC_CURRENT_IN_L] = { + .m = 6726, + .b = -537, + .R = -2, + }, + [PSC_POWER] = { + .m = 5501, + .b = -2908, + .R = -3, + }, + [PSC_POWER_L] = { + .m = 26882, + .b = -5646, + .R = -4, + }, + [PSC_TEMPERATURE] = { + .m = 1580, + .b = -14500, + .R = -2, + }, + }, + [lm25066] = { + [PSC_VOLTAGE_IN] = { + .m = 22070, + .b = -1800, + .R = -2, + }, + [PSC_VOLTAGE_OUT] = { + .m = 22070, + .b = -1800, + .R = -2, + }, + [PSC_CURRENT_IN] = { + .m = 13661, + .b = -5200, + .R = -2, + }, + [PSC_CURRENT_IN_L] = { + .m = 6854, + .b = -3100, + .R = -2, + }, + [PSC_POWER] = { + .m = 736, + .b = -3300, + .R = -2, + }, + [PSC_POWER_L] = { + .m = 369, + .b = -1900, + .R = -2, + }, + [PSC_TEMPERATURE] = { + .m = 16, + }, + }, + [lm5064] = { + [PSC_VOLTAGE_IN] = { + .m = 4611, + .b = -642, + .R = -2, + }, + [PSC_VOLTAGE_OUT] = { + .m = 4621, + .b = 423, + .R = -2, + }, + [PSC_CURRENT_IN] = { + .m = 10742, + .b = 1552, + .R = -2, + }, + [PSC_CURRENT_IN_L] = { + .m = 5456, + .b = 2118, + .R = -2, + }, + [PSC_POWER] = { + .m = 1204, + .b = 8524, + .R = -3, + }, + [PSC_POWER_L] = { + .m = 612, + .b = 11202, + .R = -3, + }, + [PSC_TEMPERATURE] = { + .m = 16, + }, + }, + [lm5066] = { + [PSC_VOLTAGE_IN] = { + .m = 4587, + .b = -1200, + .R = -2, + }, + [PSC_VOLTAGE_OUT] = { + .m = 4587, + .b = -2400, + .R = -2, + }, + [PSC_CURRENT_IN] = { + .m = 10753, + .b = -1200, + .R = -2, + }, + [PSC_CURRENT_IN_L] = { + .m = 5405, + .b = -600, + .R = -2, + }, + [PSC_POWER] = { + .m = 1204, + .b = -6000, + .R = -3, + }, + [PSC_POWER_L] = { + .m = 605, + .b = -8000, + .R = -3, + }, + [PSC_TEMPERATURE] = { + .m = 16, + }, + }, + [lm5066i] = { + [PSC_VOLTAGE_IN] = { + .m = 4617, + .b = -140, + .R = -2, + }, + [PSC_VOLTAGE_OUT] = { + .m = 4602, + .b = 500, + .R = -2, + }, + [PSC_CURRENT_IN] = { + .m = 15076, + .b = -504, + .R = -2, + }, + [PSC_CURRENT_IN_L] = { + .m = 7645, + .b = 100, + .R = -2, + }, + [PSC_POWER] = { + .m = 1701, + .b = -4000, + .R = -3, + }, + [PSC_POWER_L] = { + .m = 861, + .b = -965, + .R = -3, + }, + [PSC_TEMPERATURE] = { + .m = 16, + }, + }, +}; + +struct lm25066_data { + int id; + u16 rlimit; /* Maximum register value */ + struct pmbus_driver_info info; +}; + +#define to_lm25066_data(x) container_of(x, struct lm25066_data, info) + +static int lm25066_read_word_data(struct i2c_client *client, int page, + int phase, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + const struct lm25066_data *data = to_lm25066_data(info); + int ret; + + switch (reg) { + case PMBUS_VIRT_READ_VMON: + ret = pmbus_read_word_data(client, 0, 0xff, LM25066_READ_VAUX); + if (ret < 0) + break; + /* Adjust returned value to match VIN coefficients */ + switch (data->id) { + case lm25056: + /* VIN: 6.14 mV VAUX: 293 uV LSB */ + ret = DIV_ROUND_CLOSEST(ret * 293, 6140); + break; + case lm25066: + /* VIN: 4.54 mV VAUX: 283.2 uV LSB */ + ret = DIV_ROUND_CLOSEST(ret * 2832, 45400); + break; + case lm5064: + /* VIN: 4.53 mV VAUX: 700 uV LSB */ + ret = DIV_ROUND_CLOSEST(ret * 70, 453); + break; + case lm5066: + case lm5066i: + /* VIN: 2.18 mV VAUX: 725 uV LSB */ + ret = DIV_ROUND_CLOSEST(ret * 725, 2180); + break; + } + break; + case PMBUS_READ_IIN: + ret = pmbus_read_word_data(client, 0, 0xff, + LM25066_MFR_READ_IIN); + break; + case PMBUS_READ_PIN: + ret = pmbus_read_word_data(client, 0, 0xff, + LM25066_MFR_READ_PIN); + break; + case PMBUS_IIN_OC_WARN_LIMIT: + ret = pmbus_read_word_data(client, 0, 0xff, + LM25066_MFR_IIN_OC_WARN_LIMIT); + break; + case PMBUS_PIN_OP_WARN_LIMIT: + ret = pmbus_read_word_data(client, 0, 0xff, + LM25066_MFR_PIN_OP_WARN_LIMIT); + break; + case PMBUS_VIRT_READ_VIN_AVG: + ret = pmbus_read_word_data(client, 0, 0xff, + LM25066_READ_AVG_VIN); + break; + case PMBUS_VIRT_READ_VOUT_AVG: + ret = pmbus_read_word_data(client, 0, 0xff, + LM25066_READ_AVG_VOUT); + break; + case PMBUS_VIRT_READ_IIN_AVG: + ret = pmbus_read_word_data(client, 0, 0xff, + LM25066_READ_AVG_IIN); + break; + case PMBUS_VIRT_READ_PIN_AVG: + ret = pmbus_read_word_data(client, 0, 0xff, + LM25066_READ_AVG_PIN); + break; + case PMBUS_VIRT_READ_PIN_MAX: + ret = pmbus_read_word_data(client, 0, 0xff, + LM25066_READ_PIN_PEAK); + break; + case PMBUS_VIRT_RESET_PIN_HISTORY: + ret = 0; + break; + case PMBUS_VIRT_SAMPLES: + ret = pmbus_read_byte_data(client, 0, LM25066_SAMPLES_FOR_AVG); + if (ret < 0) + break; + ret = 1 << ret; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int lm25056_read_word_data(struct i2c_client *client, int page, + int phase, int reg) +{ + int ret; + + switch (reg) { + case PMBUS_VIRT_VMON_UV_WARN_LIMIT: + ret = pmbus_read_word_data(client, 0, 0xff, + LM25056_VAUX_UV_WARN_LIMIT); + if (ret < 0) + break; + /* Adjust returned value to match VIN coefficients */ + ret = DIV_ROUND_CLOSEST(ret * 293, 6140); + break; + case PMBUS_VIRT_VMON_OV_WARN_LIMIT: + ret = pmbus_read_word_data(client, 0, 0xff, + LM25056_VAUX_OV_WARN_LIMIT); + if (ret < 0) + break; + /* Adjust returned value to match VIN coefficients */ + ret = DIV_ROUND_CLOSEST(ret * 293, 6140); + break; + default: + ret = lm25066_read_word_data(client, page, phase, reg); + break; + } + return ret; +} + +static int lm25056_read_byte_data(struct i2c_client *client, int page, int reg) +{ + int ret, s; + + switch (reg) { + case PMBUS_VIRT_STATUS_VMON: + ret = pmbus_read_byte_data(client, 0, + PMBUS_STATUS_MFR_SPECIFIC); + if (ret < 0) + break; + s = 0; + if (ret & LM25056_MFR_STS_VAUX_UV_WARN) + s |= PB_VOLTAGE_UV_WARNING; + if (ret & LM25056_MFR_STS_VAUX_OV_WARN) + s |= PB_VOLTAGE_OV_WARNING; + ret = s; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int lm25066_write_word_data(struct i2c_client *client, int page, int reg, + u16 word) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + const struct lm25066_data *data = to_lm25066_data(info); + int ret; + + switch (reg) { + case PMBUS_POUT_OP_FAULT_LIMIT: + case PMBUS_POUT_OP_WARN_LIMIT: + case PMBUS_VOUT_UV_WARN_LIMIT: + case PMBUS_OT_FAULT_LIMIT: + case PMBUS_OT_WARN_LIMIT: + case PMBUS_IIN_OC_FAULT_LIMIT: + case PMBUS_VIN_UV_WARN_LIMIT: + case PMBUS_VIN_UV_FAULT_LIMIT: + case PMBUS_VIN_OV_FAULT_LIMIT: + case PMBUS_VIN_OV_WARN_LIMIT: + word = ((s16)word < 0) ? 0 : clamp_val(word, 0, data->rlimit); + ret = pmbus_write_word_data(client, 0, reg, word); + break; + case PMBUS_IIN_OC_WARN_LIMIT: + word = ((s16)word < 0) ? 0 : clamp_val(word, 0, data->rlimit); + ret = pmbus_write_word_data(client, 0, + LM25066_MFR_IIN_OC_WARN_LIMIT, + word); + break; + case PMBUS_PIN_OP_WARN_LIMIT: + word = ((s16)word < 0) ? 0 : clamp_val(word, 0, data->rlimit); + ret = pmbus_write_word_data(client, 0, + LM25066_MFR_PIN_OP_WARN_LIMIT, + word); + break; + case PMBUS_VIRT_VMON_UV_WARN_LIMIT: + /* Adjust from VIN coefficients (for LM25056) */ + word = DIV_ROUND_CLOSEST((int)word * 6140, 293); + word = ((s16)word < 0) ? 0 : clamp_val(word, 0, data->rlimit); + ret = pmbus_write_word_data(client, 0, + LM25056_VAUX_UV_WARN_LIMIT, word); + break; + case PMBUS_VIRT_VMON_OV_WARN_LIMIT: + /* Adjust from VIN coefficients (for LM25056) */ + word = DIV_ROUND_CLOSEST((int)word * 6140, 293); + word = ((s16)word < 0) ? 0 : clamp_val(word, 0, data->rlimit); + ret = pmbus_write_word_data(client, 0, + LM25056_VAUX_OV_WARN_LIMIT, word); + break; + case PMBUS_VIRT_RESET_PIN_HISTORY: + ret = pmbus_write_byte(client, 0, LM25066_CLEAR_PIN_PEAK); + break; + case PMBUS_VIRT_SAMPLES: + word = clamp_val(word, 1, LM25066_SAMPLES_FOR_AVG_MAX); + ret = pmbus_write_byte_data(client, 0, LM25066_SAMPLES_FOR_AVG, + ilog2(word)); + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +#if IS_ENABLED(CONFIG_SENSORS_LM25066_REGULATOR) +static const struct regulator_desc lm25066_reg_desc[] = { + PMBUS_REGULATOR("vout", 0), +}; +#endif + +static const struct i2c_device_id lm25066_id[] = { + {"lm25056", lm25056}, + {"lm25066", lm25066}, + {"lm5064", lm5064}, + {"lm5066", lm5066}, + {"lm5066i", lm5066i}, + { } +}; +MODULE_DEVICE_TABLE(i2c, lm25066_id); + +static const struct of_device_id __maybe_unused lm25066_of_match[] = { + { .compatible = "ti,lm25056", .data = (void *)lm25056, }, + { .compatible = "ti,lm25066", .data = (void *)lm25066, }, + { .compatible = "ti,lm5064", .data = (void *)lm5064, }, + { .compatible = "ti,lm5066", .data = (void *)lm5066, }, + { .compatible = "ti,lm5066i", .data = (void *)lm5066i, }, + { }, +}; +MODULE_DEVICE_TABLE(of, lm25066_of_match); + +static int lm25066_probe(struct i2c_client *client) +{ + int config; + u32 shunt; + struct lm25066_data *data; + struct pmbus_driver_info *info; + const struct __coeff *coeff; + const struct of_device_id *of_id; + const struct i2c_device_id *i2c_id; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_BYTE_DATA)) + return -ENODEV; + + data = devm_kzalloc(&client->dev, sizeof(struct lm25066_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + config = i2c_smbus_read_byte_data(client, LM25066_DEVICE_SETUP); + if (config < 0) + return config; + + i2c_id = i2c_match_id(lm25066_id, client); + + of_id = of_match_device(lm25066_of_match, &client->dev); + if (of_id && (unsigned long)of_id->data != i2c_id->driver_data) + dev_notice(&client->dev, "Device mismatch: %s in device tree, %s detected\n", + of_id->name, i2c_id->name); + + data->id = i2c_id->driver_data; + info = &data->info; + + info->pages = 1; + info->format[PSC_VOLTAGE_IN] = direct; + info->format[PSC_VOLTAGE_OUT] = direct; + info->format[PSC_CURRENT_IN] = direct; + info->format[PSC_TEMPERATURE] = direct; + info->format[PSC_POWER] = direct; + + info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VMON + | PMBUS_HAVE_PIN | PMBUS_HAVE_IIN | PMBUS_HAVE_STATUS_INPUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | PMBUS_HAVE_SAMPLES; + + if (data->id == lm25056) { + info->func[0] |= PMBUS_HAVE_STATUS_VMON; + info->read_word_data = lm25056_read_word_data; + info->read_byte_data = lm25056_read_byte_data; + data->rlimit = 0x0fff; + } else { + info->func[0] |= PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; + info->read_word_data = lm25066_read_word_data; + data->rlimit = 0x0fff; + } + info->write_word_data = lm25066_write_word_data; + + coeff = &lm25066_coeff[data->id][0]; + info->m[PSC_TEMPERATURE] = coeff[PSC_TEMPERATURE].m; + info->b[PSC_TEMPERATURE] = coeff[PSC_TEMPERATURE].b; + info->R[PSC_TEMPERATURE] = coeff[PSC_TEMPERATURE].R; + info->m[PSC_VOLTAGE_IN] = coeff[PSC_VOLTAGE_IN].m; + info->b[PSC_VOLTAGE_IN] = coeff[PSC_VOLTAGE_IN].b; + info->R[PSC_VOLTAGE_IN] = coeff[PSC_VOLTAGE_IN].R; + info->m[PSC_VOLTAGE_OUT] = coeff[PSC_VOLTAGE_OUT].m; + info->b[PSC_VOLTAGE_OUT] = coeff[PSC_VOLTAGE_OUT].b; + info->R[PSC_VOLTAGE_OUT] = coeff[PSC_VOLTAGE_OUT].R; + info->R[PSC_CURRENT_IN] = coeff[PSC_CURRENT_IN].R; + info->R[PSC_POWER] = coeff[PSC_POWER].R; + if (config & LM25066_DEV_SETUP_CL) { + info->m[PSC_CURRENT_IN] = coeff[PSC_CURRENT_IN_L].m; + info->b[PSC_CURRENT_IN] = coeff[PSC_CURRENT_IN_L].b; + info->m[PSC_POWER] = coeff[PSC_POWER_L].m; + info->b[PSC_POWER] = coeff[PSC_POWER_L].b; + } else { + info->m[PSC_CURRENT_IN] = coeff[PSC_CURRENT_IN].m; + info->b[PSC_CURRENT_IN] = coeff[PSC_CURRENT_IN].b; + info->m[PSC_POWER] = coeff[PSC_POWER].m; + info->b[PSC_POWER] = coeff[PSC_POWER].b; + } + + /* + * Values in the TI datasheets are normalized for a 1mOhm sense + * resistor; assume that unless DT specifies a value explicitly. + */ + if (of_property_read_u32(client->dev.of_node, "shunt-resistor-micro-ohms", &shunt)) + shunt = 1000; + + info->m[PSC_CURRENT_IN] = info->m[PSC_CURRENT_IN] * shunt / 1000; + info->m[PSC_POWER] = info->m[PSC_POWER] * shunt / 1000; + +#if IS_ENABLED(CONFIG_SENSORS_LM25066_REGULATOR) + /* LM25056 doesn't support OPERATION */ + if (data->id != lm25056) { + info->num_regulators = ARRAY_SIZE(lm25066_reg_desc); + info->reg_desc = lm25066_reg_desc; + } +#endif + + return pmbus_do_probe(client, info); +} + +/* This is the driver that will be inserted */ +static struct i2c_driver lm25066_driver = { + .driver = { + .name = "lm25066", + .of_match_table = of_match_ptr(lm25066_of_match), + }, + .probe_new = lm25066_probe, + .id_table = lm25066_id, +}; + +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/lt7182s.c b/drivers/hwmon/pmbus/lt7182s.c new file mode 100644 index 000000000..4cfe476fc --- /dev/null +++ b/drivers/hwmon/pmbus/lt7182s.c @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Hardware monitoring driver for Analog Devices LT7182S + * + * Copyright (c) 2022 Guenter Roeck + * + */ + +#include <linux/bits.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include "pmbus.h" + +#define LT7182S_NUM_PAGES 2 + +#define MFR_READ_EXTVCC 0xcd +#define MFR_READ_ITH 0xce +#define MFR_CONFIG_ALL_LT7182S 0xd1 +#define MFR_IOUT_PEAK 0xd7 +#define MFR_ADC_CONTROL_LT7182S 0xd8 + +#define MFR_DEBUG_TELEMETRY BIT(0) + +#define MFR_VOUT_PEAK 0xdd +#define MFR_VIN_PEAK 0xde +#define MFR_TEMPERATURE_1_PEAK 0xdf +#define MFR_CLEAR_PEAKS 0xe3 + +#define MFR_CONFIG_IEEE BIT(8) + +static int lt7182s_read_word_data(struct i2c_client *client, int page, int phase, int reg) +{ + int ret; + + switch (reg) { + case PMBUS_VIRT_READ_VMON: + if (page == 0 || page == 1) + ret = pmbus_read_word_data(client, page, phase, MFR_READ_ITH); + else + ret = pmbus_read_word_data(client, 0, phase, MFR_READ_EXTVCC); + break; + case PMBUS_VIRT_READ_IOUT_MAX: + ret = pmbus_read_word_data(client, page, phase, MFR_IOUT_PEAK); + break; + case PMBUS_VIRT_READ_VOUT_MAX: + ret = pmbus_read_word_data(client, page, phase, MFR_VOUT_PEAK); + break; + case PMBUS_VIRT_READ_VIN_MAX: + ret = pmbus_read_word_data(client, page, phase, MFR_VIN_PEAK); + break; + case PMBUS_VIRT_READ_TEMP_MAX: + ret = pmbus_read_word_data(client, page, phase, MFR_TEMPERATURE_1_PEAK); + break; + case PMBUS_VIRT_RESET_VIN_HISTORY: + ret = (page == 0) ? 0 : -ENODATA; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int lt7182s_write_word_data(struct i2c_client *client, int page, int reg, u16 word) +{ + int ret; + + switch (reg) { + case PMBUS_VIRT_RESET_VIN_HISTORY: + ret = pmbus_write_byte(client, 0, MFR_CLEAR_PEAKS); + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static struct pmbus_driver_info lt7182s_info = { + .pages = LT7182S_NUM_PAGES, + .format[PSC_VOLTAGE_IN] = linear, + .format[PSC_VOLTAGE_OUT] = linear, + .format[PSC_CURRENT_IN] = linear, + .format[PSC_CURRENT_OUT] = linear, + .format[PSC_TEMPERATURE] = linear, + .format[PSC_POWER] = linear, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | + PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_POUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP, + .func[1] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | + PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_POUT | + PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_STATUS_INPUT, + .read_word_data = lt7182s_read_word_data, + .write_word_data = lt7182s_write_word_data, +}; + +static int lt7182s_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct pmbus_driver_info *info; + 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_ID, buf); + if (ret < 0) { + dev_err(dev, "Failed to read PMBUS_MFR_ID\n"); + return ret; + } + if (ret != 3 || strncmp(buf, "ADI", 3)) { + buf[ret] = '\0'; + dev_err(dev, "Manufacturer '%s' not supported\n", buf); + return -ENODEV; + } + + ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf); + if (ret < 0) { + dev_err(dev, "Failed to read PMBUS_MFR_MODEL\n"); + return ret; + } + if (ret != 7 || strncmp(buf, "LT7182S", 7)) { + buf[ret] = '\0'; + dev_err(dev, "Model '%s' not supported\n", buf); + return -ENODEV; + } + + info = devm_kmemdup(dev, <7182s_info, + sizeof(struct pmbus_driver_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + /* Set data format to IEEE754 if configured */ + ret = i2c_smbus_read_word_data(client, MFR_CONFIG_ALL_LT7182S); + if (ret < 0) + return ret; + if (ret & MFR_CONFIG_IEEE) { + info->format[PSC_VOLTAGE_IN] = ieee754; + info->format[PSC_VOLTAGE_OUT] = ieee754; + info->format[PSC_CURRENT_IN] = ieee754; + info->format[PSC_CURRENT_OUT] = ieee754; + info->format[PSC_TEMPERATURE] = ieee754; + info->format[PSC_POWER] = ieee754; + } + + /* Enable VMON output if configured */ + ret = i2c_smbus_read_byte_data(client, MFR_ADC_CONTROL_LT7182S); + if (ret < 0) + return ret; + if (ret & MFR_DEBUG_TELEMETRY) { + info->pages = 3; + info->func[0] |= PMBUS_HAVE_VMON; + info->func[1] |= PMBUS_HAVE_VMON; + info->func[2] = PMBUS_HAVE_VMON; + } + + return pmbus_do_probe(client, info); +} + +static const struct i2c_device_id lt7182s_id[] = { + { "lt7182s", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, lt7182s_id); + +static const struct of_device_id __maybe_unused lt7182s_of_match[] = { + { .compatible = "adi,lt7182s" }, + {} +}; + +static struct i2c_driver lt7182s_driver = { + .driver = { + .name = "lt7182s", + .of_match_table = of_match_ptr(lt7182s_of_match), + }, + .probe_new = lt7182s_probe, + .id_table = lt7182s_id, +}; + +module_i2c_driver(lt7182s_driver); + +MODULE_AUTHOR("Guenter Roeck <linux@roeck-us.net>"); +MODULE_DESCRIPTION("PMBus driver for Analog Devices LT7182S"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/ltc2978.c b/drivers/hwmon/pmbus/ltc2978.c new file mode 100644 index 000000000..6d2592731 --- /dev/null +++ b/drivers/hwmon/pmbus/ltc2978.c @@ -0,0 +1,934 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for LTC2978 and compatible chips. + * + * Copyright (c) 2011 Ericsson AB. + * Copyright (c) 2013, 2014, 2015 Guenter Roeck + * Copyright (c) 2015 Linear Technology + * Copyright (c) 2018 Analog Devices Inc. + */ + +#include <linux/delay.h> +#include <linux/jiffies.h> +#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/regulator/driver.h> +#include "pmbus.h" + +enum chips { + /* Managers */ + ltc2972, ltc2974, ltc2975, ltc2977, ltc2978, ltc2979, ltc2980, + /* Controllers */ + ltc3880, ltc3882, ltc3883, ltc3884, ltc3886, ltc3887, ltc3889, ltc7880, + /* Modules */ + ltm2987, ltm4664, ltm4675, ltm4676, ltm4677, ltm4678, ltm4680, ltm4686, + ltm4700, +}; + +/* Common for all chips */ +#define LTC2978_MFR_VOUT_PEAK 0xdd +#define LTC2978_MFR_VIN_PEAK 0xde +#define LTC2978_MFR_TEMPERATURE_PEAK 0xdf +#define LTC2978_MFR_SPECIAL_ID 0xe7 /* Undocumented on LTC3882 */ +#define LTC2978_MFR_COMMON 0xef + +/* LTC2974, LTC2975, LCT2977, LTC2980, LTC2978, and LTM2987 */ +#define LTC2978_MFR_VOUT_MIN 0xfb +#define LTC2978_MFR_VIN_MIN 0xfc +#define LTC2978_MFR_TEMPERATURE_MIN 0xfd + +/* LTC2974, LTC2975 */ +#define LTC2974_MFR_IOUT_PEAK 0xd7 +#define LTC2974_MFR_IOUT_MIN 0xd8 + +/* LTC3880, LTC3882, LTC3883, LTC3887, LTM4675, and LTM4676 */ +#define LTC3880_MFR_IOUT_PEAK 0xd7 +#define LTC3880_MFR_CLEAR_PEAKS 0xe3 +#define LTC3880_MFR_TEMPERATURE2_PEAK 0xf4 + +/* LTC3883, LTC3884, LTC3886, LTC3889 and LTC7880 only */ +#define LTC3883_MFR_IIN_PEAK 0xe1 + + +/* LTC2975 only */ +#define LTC2975_MFR_IIN_PEAK 0xc4 +#define LTC2975_MFR_IIN_MIN 0xc5 +#define LTC2975_MFR_PIN_PEAK 0xc6 +#define LTC2975_MFR_PIN_MIN 0xc7 + +#define LTC2978_ID_MASK 0xfff0 + +#define LTC2972_ID 0x0310 +#define LTC2974_ID 0x0210 +#define LTC2975_ID 0x0220 +#define LTC2977_ID 0x0130 +#define LTC2978_ID_REV1 0x0110 /* Early revision */ +#define LTC2978_ID_REV2 0x0120 +#define LTC2979_ID_A 0x8060 +#define LTC2979_ID_B 0x8070 +#define LTC2980_ID_A 0x8030 /* A/B for two die IDs */ +#define LTC2980_ID_B 0x8040 +#define LTC3880_ID 0x4020 +#define LTC3882_ID 0x4200 +#define LTC3882_ID_D1 0x4240 /* Dash 1 */ +#define LTC3883_ID 0x4300 +#define LTC3884_ID 0x4C00 +#define LTC3886_ID 0x4600 +#define LTC3887_ID 0x4700 +#define LTM2987_ID_A 0x8010 /* A/B for two die IDs */ +#define LTM2987_ID_B 0x8020 +#define LTC3889_ID 0x4900 +#define LTC7880_ID 0x49E0 +#define LTM4664_ID 0x4120 +#define LTM4675_ID 0x47a0 +#define LTM4676_ID_REV1 0x4400 +#define LTM4676_ID_REV2 0x4480 +#define LTM4676A_ID 0x47e0 +#define LTM4677_ID_REV1 0x47B0 +#define LTM4677_ID_REV2 0x47D0 +#define LTM4678_ID_REV1 0x4100 +#define LTM4678_ID_REV2 0x4110 +#define LTM4680_ID 0x4140 +#define LTM4686_ID 0x4770 +#define LTM4700_ID 0x4130 + +#define LTC2972_NUM_PAGES 2 +#define LTC2974_NUM_PAGES 4 +#define LTC2978_NUM_PAGES 8 +#define LTC3880_NUM_PAGES 2 +#define LTC3883_NUM_PAGES 1 + +#define LTC_POLL_TIMEOUT 100 /* in milli-seconds */ + +#define LTC_NOT_BUSY BIT(6) +#define LTC_NOT_PENDING BIT(5) + +/* + * LTC2978 clears peak data whenever the CLEAR_FAULTS command is executed, which + * happens pretty much each time chip data is updated. Raw peak data therefore + * does not provide much value. To be able to provide useful peak data, keep an + * internal cache of measured peak data, which is only cleared if an explicit + * "clear peak" command is executed for the sensor in question. + */ + +struct ltc2978_data { + enum chips id; + u16 vin_min, vin_max; + u16 temp_min[LTC2974_NUM_PAGES], temp_max[LTC2974_NUM_PAGES]; + u16 vout_min[LTC2978_NUM_PAGES], vout_max[LTC2978_NUM_PAGES]; + u16 iout_min[LTC2974_NUM_PAGES], iout_max[LTC2974_NUM_PAGES]; + u16 iin_min, iin_max; + u16 pin_min, pin_max; + u16 temp2_max; + struct pmbus_driver_info info; + u32 features; +}; +#define to_ltc2978_data(x) container_of(x, struct ltc2978_data, info) + +#define FEAT_CLEAR_PEAKS BIT(0) +#define FEAT_NEEDS_POLLING BIT(1) + +#define has_clear_peaks(d) ((d)->features & FEAT_CLEAR_PEAKS) +#define needs_polling(d) ((d)->features & FEAT_NEEDS_POLLING) + +static int ltc_wait_ready(struct i2c_client *client) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(LTC_POLL_TIMEOUT); + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct ltc2978_data *data = to_ltc2978_data(info); + int status; + u8 mask; + + if (!needs_polling(data)) + return 0; + + /* + * LTC3883 does not support LTC_NOT_PENDING, even though + * the datasheet claims that it does. + */ + mask = LTC_NOT_BUSY; + if (data->id != ltc3883) + mask |= LTC_NOT_PENDING; + + do { + status = pmbus_read_byte_data(client, 0, LTC2978_MFR_COMMON); + if (status == -EBADMSG || status == -ENXIO) { + /* PEC error or NACK: chip may be busy, try again */ + usleep_range(50, 100); + continue; + } + if (status < 0) + return status; + + if ((status & mask) == mask) + return 0; + + usleep_range(50, 100); + } while (time_before(jiffies, timeout)); + + return -ETIMEDOUT; +} + +static int ltc_read_word_data(struct i2c_client *client, int page, int phase, + int reg) +{ + int ret; + + ret = ltc_wait_ready(client); + if (ret < 0) + return ret; + + return pmbus_read_word_data(client, page, 0xff, reg); +} + +static int ltc_read_byte_data(struct i2c_client *client, int page, int reg) +{ + int ret; + + ret = ltc_wait_ready(client); + if (ret < 0) + return ret; + + return pmbus_read_byte_data(client, page, reg); +} + +static int ltc_write_byte_data(struct i2c_client *client, int page, int reg, u8 value) +{ + int ret; + + ret = ltc_wait_ready(client); + if (ret < 0) + return ret; + + return pmbus_write_byte_data(client, page, reg, value); +} + +static int ltc_write_byte(struct i2c_client *client, int page, u8 byte) +{ + int ret; + + ret = ltc_wait_ready(client); + if (ret < 0) + return ret; + + return pmbus_write_byte(client, page, byte); +} + +static inline int lin11_to_val(int data) +{ + s16 e = ((s16)data) >> 11; + s32 m = (((s16)(data << 5)) >> 5); + + /* + * mantissa is 10 bit + sign, exponent adds up to 15 bit. + * Add 6 bit to exponent for maximum accuracy (10 + 15 + 6 = 31). + */ + e += 6; + return (e < 0 ? m >> -e : m << e); +} + +static int ltc_get_max(struct ltc2978_data *data, struct i2c_client *client, + int page, int reg, u16 *pmax) +{ + int ret; + + ret = ltc_read_word_data(client, page, 0xff, reg); + if (ret >= 0) { + if (lin11_to_val(ret) > lin11_to_val(*pmax)) + *pmax = ret; + ret = *pmax; + } + return ret; +} + +static int ltc_get_min(struct ltc2978_data *data, struct i2c_client *client, + int page, int reg, u16 *pmin) +{ + int ret; + + ret = ltc_read_word_data(client, page, 0xff, reg); + if (ret >= 0) { + if (lin11_to_val(ret) < lin11_to_val(*pmin)) + *pmin = ret; + ret = *pmin; + } + return ret; +} + +static int ltc2978_read_word_data_common(struct i2c_client *client, int page, + int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct ltc2978_data *data = to_ltc2978_data(info); + int ret; + + switch (reg) { + case PMBUS_VIRT_READ_VIN_MAX: + ret = ltc_get_max(data, client, page, LTC2978_MFR_VIN_PEAK, + &data->vin_max); + break; + case PMBUS_VIRT_READ_VOUT_MAX: + ret = ltc_read_word_data(client, page, 0xff, + LTC2978_MFR_VOUT_PEAK); + if (ret >= 0) { + /* + * VOUT is 16 bit unsigned with fixed exponent, + * so we can compare it directly + */ + if (ret > data->vout_max[page]) + data->vout_max[page] = ret; + ret = data->vout_max[page]; + } + break; + case PMBUS_VIRT_READ_TEMP_MAX: + ret = ltc_get_max(data, client, page, + LTC2978_MFR_TEMPERATURE_PEAK, + &data->temp_max[page]); + break; + case PMBUS_VIRT_RESET_VOUT_HISTORY: + case PMBUS_VIRT_RESET_VIN_HISTORY: + case PMBUS_VIRT_RESET_TEMP_HISTORY: + ret = 0; + break; + default: + ret = ltc_wait_ready(client); + if (ret < 0) + return ret; + ret = -ENODATA; + break; + } + return ret; +} + +static int ltc2978_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 ltc2978_data *data = to_ltc2978_data(info); + int ret; + + switch (reg) { + case PMBUS_VIRT_READ_VIN_MIN: + ret = ltc_get_min(data, client, page, LTC2978_MFR_VIN_MIN, + &data->vin_min); + break; + case PMBUS_VIRT_READ_VOUT_MIN: + ret = ltc_read_word_data(client, page, phase, + LTC2978_MFR_VOUT_MIN); + if (ret >= 0) { + /* + * VOUT_MIN is known to not be supported on some lots + * of LTC2978 revision 1, and will return the maximum + * possible voltage if read. If VOUT_MAX is valid and + * lower than the reading of VOUT_MIN, use it instead. + */ + if (data->vout_max[page] && ret > data->vout_max[page]) + ret = data->vout_max[page]; + if (ret < data->vout_min[page]) + data->vout_min[page] = ret; + ret = data->vout_min[page]; + } + break; + case PMBUS_VIRT_READ_TEMP_MIN: + ret = ltc_get_min(data, client, page, + LTC2978_MFR_TEMPERATURE_MIN, + &data->temp_min[page]); + break; + case PMBUS_VIRT_READ_IOUT_MAX: + case PMBUS_VIRT_RESET_IOUT_HISTORY: + case PMBUS_VIRT_READ_TEMP2_MAX: + case PMBUS_VIRT_RESET_TEMP2_HISTORY: + ret = -ENXIO; + break; + default: + ret = ltc2978_read_word_data_common(client, page, reg); + break; + } + return ret; +} + +static int ltc2974_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 ltc2978_data *data = to_ltc2978_data(info); + int ret; + + switch (reg) { + case PMBUS_VIRT_READ_IOUT_MAX: + ret = ltc_get_max(data, client, page, LTC2974_MFR_IOUT_PEAK, + &data->iout_max[page]); + break; + case PMBUS_VIRT_READ_IOUT_MIN: + ret = ltc_get_min(data, client, page, LTC2974_MFR_IOUT_MIN, + &data->iout_min[page]); + break; + case PMBUS_VIRT_RESET_IOUT_HISTORY: + ret = 0; + break; + default: + ret = ltc2978_read_word_data(client, page, phase, reg); + break; + } + return ret; +} + +static int ltc2975_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 ltc2978_data *data = to_ltc2978_data(info); + int ret; + + switch (reg) { + case PMBUS_VIRT_READ_IIN_MAX: + ret = ltc_get_max(data, client, page, LTC2975_MFR_IIN_PEAK, + &data->iin_max); + break; + case PMBUS_VIRT_READ_IIN_MIN: + ret = ltc_get_min(data, client, page, LTC2975_MFR_IIN_MIN, + &data->iin_min); + break; + case PMBUS_VIRT_READ_PIN_MAX: + ret = ltc_get_max(data, client, page, LTC2975_MFR_PIN_PEAK, + &data->pin_max); + break; + case PMBUS_VIRT_READ_PIN_MIN: + ret = ltc_get_min(data, client, page, LTC2975_MFR_PIN_MIN, + &data->pin_min); + break; + case PMBUS_VIRT_RESET_IIN_HISTORY: + case PMBUS_VIRT_RESET_PIN_HISTORY: + ret = 0; + break; + default: + ret = ltc2978_read_word_data(client, page, phase, reg); + break; + } + return ret; +} + +static int ltc3880_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 ltc2978_data *data = to_ltc2978_data(info); + int ret; + + switch (reg) { + case PMBUS_VIRT_READ_IOUT_MAX: + ret = ltc_get_max(data, client, page, LTC3880_MFR_IOUT_PEAK, + &data->iout_max[page]); + break; + case PMBUS_VIRT_READ_TEMP2_MAX: + ret = ltc_get_max(data, client, page, + LTC3880_MFR_TEMPERATURE2_PEAK, + &data->temp2_max); + break; + case PMBUS_VIRT_READ_VIN_MIN: + case PMBUS_VIRT_READ_VOUT_MIN: + case PMBUS_VIRT_READ_TEMP_MIN: + ret = -ENXIO; + break; + case PMBUS_VIRT_RESET_IOUT_HISTORY: + case PMBUS_VIRT_RESET_TEMP2_HISTORY: + ret = 0; + break; + default: + ret = ltc2978_read_word_data_common(client, page, reg); + break; + } + return ret; +} + +static int ltc3883_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 ltc2978_data *data = to_ltc2978_data(info); + int ret; + + switch (reg) { + case PMBUS_VIRT_READ_IIN_MAX: + ret = ltc_get_max(data, client, page, LTC3883_MFR_IIN_PEAK, + &data->iin_max); + break; + case PMBUS_VIRT_RESET_IIN_HISTORY: + ret = 0; + break; + default: + ret = ltc3880_read_word_data(client, page, phase, reg); + break; + } + return ret; +} + +static int ltc2978_clear_peaks(struct ltc2978_data *data, + struct i2c_client *client, int page) +{ + int ret; + + if (has_clear_peaks(data)) + ret = ltc_write_byte(client, 0, LTC3880_MFR_CLEAR_PEAKS); + else + ret = ltc_write_byte(client, page, PMBUS_CLEAR_FAULTS); + + return ret; +} + +static int ltc2978_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 ltc2978_data *data = to_ltc2978_data(info); + int ret; + + switch (reg) { + case PMBUS_VIRT_RESET_IIN_HISTORY: + data->iin_max = 0x7c00; + data->iin_min = 0x7bff; + ret = ltc2978_clear_peaks(data, client, 0); + break; + case PMBUS_VIRT_RESET_PIN_HISTORY: + data->pin_max = 0x7c00; + data->pin_min = 0x7bff; + ret = ltc2978_clear_peaks(data, client, 0); + break; + case PMBUS_VIRT_RESET_IOUT_HISTORY: + data->iout_max[page] = 0x7c00; + data->iout_min[page] = 0xfbff; + ret = ltc2978_clear_peaks(data, client, page); + break; + case PMBUS_VIRT_RESET_TEMP2_HISTORY: + data->temp2_max = 0x7c00; + ret = ltc2978_clear_peaks(data, client, page); + break; + case PMBUS_VIRT_RESET_VOUT_HISTORY: + data->vout_min[page] = 0xffff; + data->vout_max[page] = 0; + ret = ltc2978_clear_peaks(data, client, page); + break; + case PMBUS_VIRT_RESET_VIN_HISTORY: + data->vin_min = 0x7bff; + data->vin_max = 0x7c00; + ret = ltc2978_clear_peaks(data, client, page); + break; + case PMBUS_VIRT_RESET_TEMP_HISTORY: + data->temp_min[page] = 0x7bff; + data->temp_max[page] = 0x7c00; + ret = ltc2978_clear_peaks(data, client, page); + break; + default: + ret = ltc_wait_ready(client); + if (ret < 0) + return ret; + ret = -ENODATA; + break; + } + return ret; +} + +static const struct i2c_device_id ltc2978_id[] = { + {"ltc2972", ltc2972}, + {"ltc2974", ltc2974}, + {"ltc2975", ltc2975}, + {"ltc2977", ltc2977}, + {"ltc2978", ltc2978}, + {"ltc2979", ltc2979}, + {"ltc2980", ltc2980}, + {"ltc3880", ltc3880}, + {"ltc3882", ltc3882}, + {"ltc3883", ltc3883}, + {"ltc3884", ltc3884}, + {"ltc3886", ltc3886}, + {"ltc3887", ltc3887}, + {"ltc3889", ltc3889}, + {"ltc7880", ltc7880}, + {"ltm2987", ltm2987}, + {"ltm4664", ltm4664}, + {"ltm4675", ltm4675}, + {"ltm4676", ltm4676}, + {"ltm4677", ltm4677}, + {"ltm4678", ltm4678}, + {"ltm4680", ltm4680}, + {"ltm4686", ltm4686}, + {"ltm4700", ltm4700}, + {} +}; +MODULE_DEVICE_TABLE(i2c, ltc2978_id); + +#if IS_ENABLED(CONFIG_SENSORS_LTC2978_REGULATOR) +#define LTC2978_ADC_RES 0xFFFF +#define LTC2978_N_ADC 122 +#define LTC2978_MAX_UV (LTC2978_ADC_RES * LTC2978_N_ADC) +#define LTC2978_UV_STEP 1000 +#define LTC2978_N_VOLTAGES ((LTC2978_MAX_UV / LTC2978_UV_STEP) + 1) + +static const struct regulator_desc ltc2978_reg_desc[] = { + PMBUS_REGULATOR_STEP("vout", 0, LTC2978_N_VOLTAGES, LTC2978_UV_STEP), + PMBUS_REGULATOR_STEP("vout", 1, LTC2978_N_VOLTAGES, LTC2978_UV_STEP), + PMBUS_REGULATOR_STEP("vout", 2, LTC2978_N_VOLTAGES, LTC2978_UV_STEP), + PMBUS_REGULATOR_STEP("vout", 3, LTC2978_N_VOLTAGES, LTC2978_UV_STEP), + PMBUS_REGULATOR_STEP("vout", 4, LTC2978_N_VOLTAGES, LTC2978_UV_STEP), + PMBUS_REGULATOR_STEP("vout", 5, LTC2978_N_VOLTAGES, LTC2978_UV_STEP), + PMBUS_REGULATOR_STEP("vout", 6, LTC2978_N_VOLTAGES, LTC2978_UV_STEP), + PMBUS_REGULATOR_STEP("vout", 7, LTC2978_N_VOLTAGES, LTC2978_UV_STEP), +}; + +static const struct regulator_desc ltc2978_reg_desc_default[] = { + PMBUS_REGULATOR("vout", 0), + PMBUS_REGULATOR("vout", 1), + PMBUS_REGULATOR("vout", 2), + PMBUS_REGULATOR("vout", 3), + PMBUS_REGULATOR("vout", 4), + PMBUS_REGULATOR("vout", 5), + PMBUS_REGULATOR("vout", 6), + PMBUS_REGULATOR("vout", 7), +}; +#endif /* CONFIG_SENSORS_LTC2978_REGULATOR */ + +static int ltc2978_get_id(struct i2c_client *client) +{ + int chip_id; + + chip_id = i2c_smbus_read_word_data(client, LTC2978_MFR_SPECIAL_ID); + if (chip_id < 0) { + const struct i2c_device_id *id; + u8 buf[I2C_SMBUS_BLOCK_MAX]; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_BLOCK_DATA)) + return -ENODEV; + + ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf); + if (ret < 0) + return ret; + if (ret < 3 || strncmp(buf, "LTC", 3)) + return -ENODEV; + + ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf); + if (ret < 0) + return ret; + for (id = <c2978_id[0]; strlen(id->name); id++) { + if (!strncasecmp(id->name, buf, strlen(id->name))) + return (int)id->driver_data; + } + return -ENODEV; + } + + chip_id &= LTC2978_ID_MASK; + + if (chip_id == LTC2972_ID) + return ltc2972; + else if (chip_id == LTC2974_ID) + return ltc2974; + else if (chip_id == LTC2975_ID) + return ltc2975; + else if (chip_id == LTC2977_ID) + return ltc2977; + else if (chip_id == LTC2978_ID_REV1 || chip_id == LTC2978_ID_REV2) + return ltc2978; + else if (chip_id == LTC2979_ID_A || chip_id == LTC2979_ID_B) + return ltc2979; + else if (chip_id == LTC2980_ID_A || chip_id == LTC2980_ID_B) + return ltc2980; + else if (chip_id == LTC3880_ID) + return ltc3880; + else if (chip_id == LTC3882_ID || chip_id == LTC3882_ID_D1) + return ltc3882; + else if (chip_id == LTC3883_ID) + return ltc3883; + else if (chip_id == LTC3884_ID) + return ltc3884; + else if (chip_id == LTC3886_ID) + return ltc3886; + else if (chip_id == LTC3887_ID) + return ltc3887; + else if (chip_id == LTC3889_ID) + return ltc3889; + else if (chip_id == LTC7880_ID) + return ltc7880; + else if (chip_id == LTM2987_ID_A || chip_id == LTM2987_ID_B) + return ltm2987; + else if (chip_id == LTM4664_ID) + return ltm4664; + else if (chip_id == LTM4675_ID) + return ltm4675; + else if (chip_id == LTM4676_ID_REV1 || chip_id == LTM4676_ID_REV2 || + chip_id == LTM4676A_ID) + return ltm4676; + else if (chip_id == LTM4677_ID_REV1 || chip_id == LTM4677_ID_REV2) + return ltm4677; + else if (chip_id == LTM4678_ID_REV1 || chip_id == LTM4678_ID_REV2) + return ltm4678; + else if (chip_id == LTM4680_ID) + return ltm4680; + else if (chip_id == LTM4686_ID) + return ltm4686; + else if (chip_id == LTM4700_ID) + return ltm4700; + + dev_err(&client->dev, "Unsupported chip ID 0x%x\n", chip_id); + return -ENODEV; +} + +static int ltc2978_probe(struct i2c_client *client) +{ + int i, chip_id; + struct ltc2978_data *data; + struct pmbus_driver_info *info; + const struct i2c_device_id *id; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_WORD_DATA)) + return -ENODEV; + + data = devm_kzalloc(&client->dev, sizeof(struct ltc2978_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + chip_id = ltc2978_get_id(client); + if (chip_id < 0) + return chip_id; + + data->id = chip_id; + id = i2c_match_id(ltc2978_id, client); + if (data->id != id->driver_data) + dev_warn(&client->dev, + "Device mismatch: Configured %s (%d), detected %d\n", + id->name, + (int) id->driver_data, + chip_id); + + info = &data->info; + info->write_word_data = ltc2978_write_word_data; + info->write_byte = ltc_write_byte; + info->write_byte_data = ltc_write_byte_data; + info->read_word_data = ltc_read_word_data; + info->read_byte_data = ltc_read_byte_data; + + data->vin_min = 0x7bff; + data->vin_max = 0x7c00; + for (i = 0; i < ARRAY_SIZE(data->vout_min); i++) + data->vout_min[i] = 0xffff; + for (i = 0; i < ARRAY_SIZE(data->iout_min); i++) + data->iout_min[i] = 0xfbff; + for (i = 0; i < ARRAY_SIZE(data->iout_max); i++) + data->iout_max[i] = 0x7c00; + for (i = 0; i < ARRAY_SIZE(data->temp_min); i++) + data->temp_min[i] = 0x7bff; + for (i = 0; i < ARRAY_SIZE(data->temp_max); i++) + data->temp_max[i] = 0x7c00; + data->temp2_max = 0x7c00; + + switch (data->id) { + case ltc2972: + info->read_word_data = ltc2975_read_word_data; + info->pages = LTC2972_NUM_PAGES; + info->func[0] = PMBUS_HAVE_IIN | PMBUS_HAVE_PIN + | PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT + | PMBUS_HAVE_TEMP2; + for (i = 0; i < info->pages; i++) { + info->func[i] |= PMBUS_HAVE_VOUT + | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_POUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT; + } + break; + case ltc2974: + info->read_word_data = ltc2974_read_word_data; + info->pages = LTC2974_NUM_PAGES; + info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT + | PMBUS_HAVE_TEMP2; + for (i = 0; i < info->pages; i++) { + info->func[i] |= PMBUS_HAVE_VOUT + | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_POUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT; + } + break; + case ltc2975: + info->read_word_data = ltc2975_read_word_data; + info->pages = LTC2974_NUM_PAGES; + info->func[0] = PMBUS_HAVE_IIN | PMBUS_HAVE_PIN + | PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT + | PMBUS_HAVE_TEMP2; + for (i = 0; i < info->pages; i++) { + info->func[i] |= PMBUS_HAVE_VOUT + | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_POUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT; + } + break; + + case ltc2977: + case ltc2978: + case ltc2979: + case ltc2980: + case ltm2987: + info->read_word_data = ltc2978_read_word_data; + info->pages = LTC2978_NUM_PAGES; + info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT + | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + for (i = 1; i < LTC2978_NUM_PAGES; i++) { + info->func[i] = PMBUS_HAVE_VOUT + | PMBUS_HAVE_STATUS_VOUT; + } + break; + case ltc3880: + case ltc3887: + case ltm4675: + case ltm4676: + case ltm4677: + case ltm4686: + data->features |= FEAT_CLEAR_PEAKS | FEAT_NEEDS_POLLING; + info->read_word_data = ltc3880_read_word_data; + info->pages = LTC3880_NUM_PAGES; + info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN + | PMBUS_HAVE_STATUS_INPUT + | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT + | PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP + | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP; + info->func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT + | PMBUS_HAVE_POUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + break; + case ltc3882: + data->features |= FEAT_CLEAR_PEAKS | FEAT_NEEDS_POLLING; + info->read_word_data = ltc3880_read_word_data; + info->pages = LTC3880_NUM_PAGES; + info->func[0] = PMBUS_HAVE_VIN + | PMBUS_HAVE_STATUS_INPUT + | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT + | PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP + | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP; + info->func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT + | PMBUS_HAVE_POUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + break; + case ltc3883: + data->features |= FEAT_CLEAR_PEAKS | FEAT_NEEDS_POLLING; + info->read_word_data = ltc3883_read_word_data; + info->pages = LTC3883_NUM_PAGES; + info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN + | PMBUS_HAVE_STATUS_INPUT + | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT + | PMBUS_HAVE_PIN | PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP + | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP; + break; + case ltc3884: + case ltc3886: + case ltc3889: + case ltc7880: + case ltm4664: + case ltm4678: + case ltm4680: + case ltm4700: + data->features |= FEAT_CLEAR_PEAKS | FEAT_NEEDS_POLLING; + info->read_word_data = ltc3883_read_word_data; + info->pages = LTC3880_NUM_PAGES; + info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN + | PMBUS_HAVE_STATUS_INPUT + | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT + | PMBUS_HAVE_PIN | PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP + | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP; + info->func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT + | PMBUS_HAVE_POUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + break; + default: + return -ENODEV; + } + +#if IS_ENABLED(CONFIG_SENSORS_LTC2978_REGULATOR) + info->num_regulators = info->pages; + switch (data->id) { + case ltc2972: + case ltc2974: + case ltc2975: + case ltc2977: + case ltc2978: + case ltc2979: + case ltc2980: + case ltm2987: + info->reg_desc = ltc2978_reg_desc; + if (info->num_regulators > ARRAY_SIZE(ltc2978_reg_desc)) { + dev_warn(&client->dev, "num_regulators too large!"); + info->num_regulators = ARRAY_SIZE(ltc2978_reg_desc); + } + break; + default: + info->reg_desc = ltc2978_reg_desc_default; + if (info->num_regulators > ARRAY_SIZE(ltc2978_reg_desc_default)) { + dev_warn(&client->dev, "num_regulators too large!"); + info->num_regulators = + ARRAY_SIZE(ltc2978_reg_desc_default); + } + break; + } +#endif + + return pmbus_do_probe(client, info); +} + + +#ifdef CONFIG_OF +static const struct of_device_id ltc2978_of_match[] = { + { .compatible = "lltc,ltc2972" }, + { .compatible = "lltc,ltc2974" }, + { .compatible = "lltc,ltc2975" }, + { .compatible = "lltc,ltc2977" }, + { .compatible = "lltc,ltc2978" }, + { .compatible = "lltc,ltc2979" }, + { .compatible = "lltc,ltc2980" }, + { .compatible = "lltc,ltc3880" }, + { .compatible = "lltc,ltc3882" }, + { .compatible = "lltc,ltc3883" }, + { .compatible = "lltc,ltc3884" }, + { .compatible = "lltc,ltc3886" }, + { .compatible = "lltc,ltc3887" }, + { .compatible = "lltc,ltc3889" }, + { .compatible = "lltc,ltc7880" }, + { .compatible = "lltc,ltm2987" }, + { .compatible = "lltc,ltm4664" }, + { .compatible = "lltc,ltm4675" }, + { .compatible = "lltc,ltm4676" }, + { .compatible = "lltc,ltm4677" }, + { .compatible = "lltc,ltm4678" }, + { .compatible = "lltc,ltm4680" }, + { .compatible = "lltc,ltm4686" }, + { .compatible = "lltc,ltm4700" }, + { } +}; +MODULE_DEVICE_TABLE(of, ltc2978_of_match); +#endif + +static struct i2c_driver ltc2978_driver = { + .driver = { + .name = "ltc2978", + .of_match_table = of_match_ptr(ltc2978_of_match), + }, + .probe_new = ltc2978_probe, + .id_table = ltc2978_id, +}; + +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 new file mode 100644 index 000000000..8e13a7ddc --- /dev/null +++ b/drivers/hwmon/pmbus/ltc3815.c @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for LTC3815 + * + * Copyright (c) 2015 Linear Technology + * Copyright (c) 2015 Guenter Roeck + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include "pmbus.h" + +#define LTC3815_MFR_IOUT_PEAK 0xd7 +#define LTC3815_MFR_VOUT_PEAK 0xdd +#define LTC3815_MFR_VIN_PEAK 0xde +#define LTC3815_MFR_TEMP_PEAK 0xdf +#define LTC3815_MFR_IIN_PEAK 0xe1 +#define LTC3815_MFR_SPECIAL_ID 0xe7 + +#define LTC3815_ID 0x8000 +#define LTC3815_ID_MASK 0xff00 + +static int ltc3815_read_byte_data(struct i2c_client *client, int page, int reg) +{ + int ret; + + switch (reg) { + case PMBUS_VOUT_MODE: + /* + * The chip returns 0x3e, suggesting VID mode with manufacturer + * specific VID codes. Since the output voltage is reported + * with a LSB of 0.5mV, override and report direct mode with + * appropriate coefficients. + */ + ret = 0x40; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int ltc3815_write_byte(struct i2c_client *client, int page, u8 reg) +{ + int ret; + + switch (reg) { + case PMBUS_CLEAR_FAULTS: + /* + * LTC3815 does not support the CLEAR_FAULTS command. + * Emulate it by clearing the status register. + */ + ret = pmbus_read_word_data(client, 0, 0xff, PMBUS_STATUS_WORD); + if (ret > 0) { + pmbus_write_word_data(client, 0, PMBUS_STATUS_WORD, + ret); + ret = 0; + } + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int ltc3815_read_word_data(struct i2c_client *client, int page, + int phase, int reg) +{ + int ret; + + switch (reg) { + case PMBUS_VIRT_READ_VIN_MAX: + ret = pmbus_read_word_data(client, page, phase, + LTC3815_MFR_VIN_PEAK); + break; + case PMBUS_VIRT_READ_VOUT_MAX: + ret = pmbus_read_word_data(client, page, phase, + LTC3815_MFR_VOUT_PEAK); + break; + case PMBUS_VIRT_READ_TEMP_MAX: + ret = pmbus_read_word_data(client, page, phase, + LTC3815_MFR_TEMP_PEAK); + break; + case PMBUS_VIRT_READ_IOUT_MAX: + ret = pmbus_read_word_data(client, page, phase, + LTC3815_MFR_IOUT_PEAK); + break; + case PMBUS_VIRT_READ_IIN_MAX: + ret = pmbus_read_word_data(client, page, phase, + LTC3815_MFR_IIN_PEAK); + break; + case PMBUS_VIRT_RESET_VOUT_HISTORY: + case PMBUS_VIRT_RESET_VIN_HISTORY: + case PMBUS_VIRT_RESET_TEMP_HISTORY: + case PMBUS_VIRT_RESET_IOUT_HISTORY: + case PMBUS_VIRT_RESET_IIN_HISTORY: + ret = 0; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int ltc3815_write_word_data(struct i2c_client *client, int page, + int reg, u16 word) +{ + int ret; + + switch (reg) { + case PMBUS_VIRT_RESET_IIN_HISTORY: + ret = pmbus_write_word_data(client, page, + LTC3815_MFR_IIN_PEAK, 0); + break; + case PMBUS_VIRT_RESET_IOUT_HISTORY: + ret = pmbus_write_word_data(client, page, + LTC3815_MFR_IOUT_PEAK, 0); + break; + case PMBUS_VIRT_RESET_VOUT_HISTORY: + ret = pmbus_write_word_data(client, page, + LTC3815_MFR_VOUT_PEAK, 0); + break; + case PMBUS_VIRT_RESET_VIN_HISTORY: + ret = pmbus_write_word_data(client, page, + LTC3815_MFR_VIN_PEAK, 0); + break; + case PMBUS_VIRT_RESET_TEMP_HISTORY: + ret = pmbus_write_word_data(client, page, + LTC3815_MFR_TEMP_PEAK, 0); + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static const struct i2c_device_id ltc3815_id[] = { + {"ltc3815", 0}, + { } +}; +MODULE_DEVICE_TABLE(i2c, ltc3815_id); + +static struct pmbus_driver_info ltc3815_info = { + .pages = 1, + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_CURRENT_IN] = direct, + .format[PSC_CURRENT_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .m[PSC_VOLTAGE_IN] = 250, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 0, + .m[PSC_VOLTAGE_OUT] = 2, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 3, + .m[PSC_CURRENT_IN] = 1, + .b[PSC_CURRENT_IN] = 0, + .R[PSC_CURRENT_IN] = 2, + .m[PSC_CURRENT_OUT] = 1, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = 2, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 0, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN | PMBUS_HAVE_VOUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP, + .read_byte_data = ltc3815_read_byte_data, + .read_word_data = ltc3815_read_word_data, + .write_byte = ltc3815_write_byte, + .write_word_data = ltc3815_write_word_data, +}; + +static int ltc3815_probe(struct i2c_client *client) +{ + int chip_id; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_WORD_DATA)) + return -ENODEV; + + chip_id = i2c_smbus_read_word_data(client, LTC3815_MFR_SPECIAL_ID); + if (chip_id < 0) + return chip_id; + if ((chip_id & LTC3815_ID_MASK) != LTC3815_ID) + return -ENODEV; + + return pmbus_do_probe(client, <c3815_info); +} + +static struct i2c_driver ltc3815_driver = { + .driver = { + .name = "ltc3815", + }, + .probe_new = ltc3815_probe, + .id_table = ltc3815_id, +}; + +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 000000000..0b6f88428 --- /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 new file mode 100644 index 000000000..94f869039 --- /dev/null +++ b/drivers/hwmon/pmbus/max16064.c @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for Maxim MAX16064 + * + * Copyright (c) 2011 Ericsson AB. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include "pmbus.h" + +#define MAX16064_MFR_VOUT_PEAK 0xd4 +#define MAX16064_MFR_TEMPERATURE_PEAK 0xd6 + +static int max16064_read_word_data(struct i2c_client *client, int page, + int phase, int reg) +{ + int ret; + + switch (reg) { + case PMBUS_VIRT_READ_VOUT_MAX: + ret = pmbus_read_word_data(client, page, phase, + MAX16064_MFR_VOUT_PEAK); + break; + case PMBUS_VIRT_READ_TEMP_MAX: + ret = pmbus_read_word_data(client, page, phase, + MAX16064_MFR_TEMPERATURE_PEAK); + break; + case PMBUS_VIRT_RESET_VOUT_HISTORY: + case PMBUS_VIRT_RESET_TEMP_HISTORY: + ret = 0; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int max16064_write_word_data(struct i2c_client *client, int page, + int reg, u16 word) +{ + int ret; + + switch (reg) { + case PMBUS_VIRT_RESET_VOUT_HISTORY: + ret = pmbus_write_word_data(client, page, + MAX16064_MFR_VOUT_PEAK, 0); + break; + case PMBUS_VIRT_RESET_TEMP_HISTORY: + ret = pmbus_write_word_data(client, page, + MAX16064_MFR_TEMPERATURE_PEAK, + 0xffff); + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static struct pmbus_driver_info max16064_info = { + .pages = 4, + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .m[PSC_VOLTAGE_IN] = 19995, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = -1, + .m[PSC_VOLTAGE_OUT] = 19995, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = -1, + .m[PSC_TEMPERATURE] = -7612, + .b[PSC_TEMPERATURE] = 335, + .R[PSC_TEMPERATURE] = -3, + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_TEMP + | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_TEMP, + .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[3] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .read_word_data = max16064_read_word_data, + .write_word_data = max16064_write_word_data, +}; + +static int max16064_probe(struct i2c_client *client) +{ + return pmbus_do_probe(client, &max16064_info); +} + +static const struct i2c_device_id max16064_id[] = { + {"max16064", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, max16064_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver max16064_driver = { + .driver = { + .name = "max16064", + }, + .probe_new = max16064_probe, + .id_table = max16064_id, +}; + +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 new file mode 100644 index 000000000..b628405e6 --- /dev/null +++ b/drivers/hwmon/pmbus/max16601.c @@ -0,0 +1,365 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Hardware monitoring driver for Maxim MAX16508, MAX16601 and MAX16602. + * + * Implementation notes: + * + * This chip series supports two rails, VCORE and VSA. Telemetry information + * for the two rails is reported in two subsequent I2C addresses. The driver + * instantiates a dummy I2C client at the second I2C address to report + * information for the VSA rail in a single instance of the driver. + * Telemetry for the VSA rail is reported to the PMBus core in PMBus page 2. + * + * The chip reports input current using two separate methods. The input current + * reported with the standard READ_IIN command is derived from the output + * current. The first method is reported to the PMBus core with PMBus page 0, + * the second method is reported with PMBus page 1. + * + * The chip supports reading per-phase temperatures and per-phase input/output + * currents for VCORE. Telemetry is reported in vendor specific registers. + * The driver translates the vendor specific register values to PMBus standard + * register values and reports per-phase information in PMBus page 0. + * + * Copyright 2019, 2020 Google LLC. + */ + +#include <linux/bits.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> + +#include "pmbus.h" + +enum chips { max16508, max16601, max16602 }; + +#define REG_DEFAULT_NUM_POP 0xc4 +#define REG_SETPT_DVID 0xd1 +#define DAC_10MV_MODE BIT(4) +#define REG_IOUT_AVG_PK 0xee +#define REG_IIN_SENSOR 0xf1 +#define REG_TOTAL_INPUT_POWER 0xf2 +#define REG_PHASE_ID 0xf3 +#define CORE_RAIL_INDICATOR BIT(7) +#define REG_PHASE_REPORTING 0xf4 + +#define MAX16601_NUM_PHASES 8 + +struct max16601_data { + enum chips id; + struct pmbus_driver_info info; + struct i2c_client *vsa; + int iout_avg_pkg; +}; + +#define to_max16601_data(x) container_of(x, struct max16601_data, info) + +static int max16601_read_byte(struct i2c_client *client, int page, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct max16601_data *data = to_max16601_data(info); + + if (page > 0) { + if (page == 2) /* VSA */ + return i2c_smbus_read_byte_data(data->vsa, reg); + return -EOPNOTSUPP; + } + return -ENODATA; +} + +static int max16601_read_word(struct i2c_client *client, int page, int phase, + int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct max16601_data *data = to_max16601_data(info); + u8 buf[I2C_SMBUS_BLOCK_MAX + 1]; + int ret; + + switch (page) { + case 0: /* VCORE */ + if (phase == 0xff) + return -ENODATA; + switch (reg) { + case PMBUS_READ_IIN: + case PMBUS_READ_IOUT: + case PMBUS_READ_TEMPERATURE_1: + ret = i2c_smbus_write_byte_data(client, REG_PHASE_ID, + phase); + if (ret) + return ret; + ret = i2c_smbus_read_block_data(client, + REG_PHASE_REPORTING, + buf); + if (ret < 0) + return ret; + if (ret < 6) + return -EIO; + switch (reg) { + case PMBUS_READ_TEMPERATURE_1: + return buf[1] << 8 | buf[0]; + case PMBUS_READ_IOUT: + return buf[3] << 8 | buf[2]; + case PMBUS_READ_IIN: + return buf[5] << 8 | buf[4]; + default: + break; + } + } + return -EOPNOTSUPP; + case 1: /* VCORE, read IIN/PIN from sensor element */ + switch (reg) { + case PMBUS_READ_IIN: + return i2c_smbus_read_word_data(client, REG_IIN_SENSOR); + case PMBUS_READ_PIN: + return i2c_smbus_read_word_data(client, + REG_TOTAL_INPUT_POWER); + default: + break; + } + return -EOPNOTSUPP; + case 2: /* VSA */ + switch (reg) { + case PMBUS_VIRT_READ_IOUT_MAX: + ret = i2c_smbus_read_word_data(data->vsa, + REG_IOUT_AVG_PK); + if (ret < 0) + return ret; + if (sign_extend32(ret, 10) > + sign_extend32(data->iout_avg_pkg, 10)) + data->iout_avg_pkg = ret; + return data->iout_avg_pkg; + case PMBUS_VIRT_RESET_IOUT_HISTORY: + return 0; + case PMBUS_IOUT_OC_FAULT_LIMIT: + case PMBUS_IOUT_OC_WARN_LIMIT: + case PMBUS_OT_FAULT_LIMIT: + case PMBUS_OT_WARN_LIMIT: + case PMBUS_READ_IIN: + case PMBUS_READ_IOUT: + case PMBUS_READ_TEMPERATURE_1: + case PMBUS_STATUS_WORD: + return i2c_smbus_read_word_data(data->vsa, reg); + default: + return -EOPNOTSUPP; + } + default: + return -EOPNOTSUPP; + } +} + +static int max16601_write_byte(struct i2c_client *client, int page, u8 reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct max16601_data *data = to_max16601_data(info); + + if (page == 2) { + if (reg == PMBUS_CLEAR_FAULTS) + return i2c_smbus_write_byte(data->vsa, reg); + return -EOPNOTSUPP; + } + return -ENODATA; +} + +static int max16601_write_word(struct i2c_client *client, int page, int reg, + u16 value) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct max16601_data *data = to_max16601_data(info); + + switch (page) { + case 0: /* VCORE */ + return -ENODATA; + case 1: /* VCORE IIN/PIN from sensor element */ + default: + return -EOPNOTSUPP; + case 2: /* VSA */ + switch (reg) { + case PMBUS_VIRT_RESET_IOUT_HISTORY: + data->iout_avg_pkg = 0xfc00; + return 0; + case PMBUS_IOUT_OC_FAULT_LIMIT: + case PMBUS_IOUT_OC_WARN_LIMIT: + case PMBUS_OT_FAULT_LIMIT: + case PMBUS_OT_WARN_LIMIT: + return i2c_smbus_write_word_data(data->vsa, reg, value); + default: + return -EOPNOTSUPP; + } + } +} + +static int max16601_identify(struct i2c_client *client, + struct pmbus_driver_info *info) +{ + struct max16601_data *data = to_max16601_data(info); + int reg; + + reg = i2c_smbus_read_byte_data(client, REG_SETPT_DVID); + if (reg < 0) + return reg; + if (reg & DAC_10MV_MODE) + info->vrm_version[0] = vr13; + else + info->vrm_version[0] = vr12; + + if (data->id != max16601 && data->id != max16602) + return 0; + + reg = i2c_smbus_read_byte_data(client, REG_DEFAULT_NUM_POP); + if (reg < 0) + return reg; + + /* + * If REG_DEFAULT_NUM_POP returns 0, we don't know how many phases + * are populated. Stick with the default in that case. + */ + reg &= 0x0f; + if (reg && reg <= MAX16601_NUM_PHASES) + info->phases[0] = reg; + + return 0; +} + +static struct pmbus_driver_info max16601_info = { + .pages = 3, + .format[PSC_VOLTAGE_IN] = linear, + .format[PSC_VOLTAGE_OUT] = vid, + .format[PSC_CURRENT_IN] = linear, + .format[PSC_CURRENT_OUT] = linear, + .format[PSC_TEMPERATURE] = linear, + .format[PSC_POWER] = linear, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN | PMBUS_HAVE_PIN | + PMBUS_HAVE_STATUS_INPUT | + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_POUT | PMBUS_PAGE_VIRTUAL | PMBUS_PHASE_VIRTUAL, + .func[1] = PMBUS_HAVE_IIN | PMBUS_HAVE_PIN | PMBUS_PAGE_VIRTUAL, + .func[2] = PMBUS_HAVE_IIN | PMBUS_HAVE_STATUS_INPUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | PMBUS_PAGE_VIRTUAL, + .phases[0] = MAX16601_NUM_PHASES, + .pfunc[0] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP, + .pfunc[1] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT, + .pfunc[2] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP, + .pfunc[3] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT, + .pfunc[4] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP, + .pfunc[5] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT, + .pfunc[6] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP, + .pfunc[7] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT, + .identify = max16601_identify, + .read_byte_data = max16601_read_byte, + .read_word_data = max16601_read_word, + .write_byte = max16601_write_byte, + .write_word_data = max16601_write_word, +}; + +static void max16601_remove(void *_data) +{ + struct max16601_data *data = _data; + + i2c_unregister_device(data->vsa); +} + +static const struct i2c_device_id max16601_id[] = { + {"max16508", max16508}, + {"max16601", max16601}, + {"max16602", max16602}, + {} +}; +MODULE_DEVICE_TABLE(i2c, max16601_id); + +static int max16601_get_id(struct i2c_client *client) +{ + struct device *dev = &client->dev; + u8 buf[I2C_SMBUS_BLOCK_MAX + 1]; + enum chips id; + int ret; + + ret = i2c_smbus_read_block_data(client, PMBUS_IC_DEVICE_ID, buf); + if (ret < 0 || ret < 11) + return -ENODEV; + + /* + * PMBUS_IC_DEVICE_ID is expected to return "MAX16601y.xx" or + * MAX16602y.xx or "MAX16500y.xx".cdxxcccccccccc + */ + if (!strncmp(buf, "MAX16500", 8)) { + id = max16508; + } else if (!strncmp(buf, "MAX16601", 8)) { + id = max16601; + } else if (!strncmp(buf, "MAX16602", 8)) { + id = max16602; + } else { + buf[ret] = '\0'; + dev_err(dev, "Unsupported chip '%s'\n", buf); + return -ENODEV; + } + return id; +} + +static int max16601_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + const struct i2c_device_id *id; + struct max16601_data *data; + int ret, chip_id; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_BYTE_DATA | + I2C_FUNC_SMBUS_READ_BLOCK_DATA)) + return -ENODEV; + + chip_id = max16601_get_id(client); + if (chip_id < 0) + return chip_id; + + id = i2c_match_id(max16601_id, client); + if (chip_id != id->driver_data) + dev_warn(&client->dev, + "Device mismatch: Configured %s (%d), detected %d\n", + id->name, (int) id->driver_data, chip_id); + + ret = i2c_smbus_read_byte_data(client, REG_PHASE_ID); + if (ret < 0) + return ret; + if (!(ret & CORE_RAIL_INDICATOR)) { + dev_err(dev, + "Driver must be instantiated on CORE rail I2C address\n"); + return -ENODEV; + } + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->id = chip_id; + data->iout_avg_pkg = 0xfc00; + data->vsa = i2c_new_dummy_device(client->adapter, client->addr + 1); + if (IS_ERR(data->vsa)) { + dev_err(dev, "Failed to register VSA client\n"); + return PTR_ERR(data->vsa); + } + ret = devm_add_action_or_reset(dev, max16601_remove, data); + if (ret) + return ret; + + data->info = max16601_info; + + return pmbus_do_probe(client, &data->info); +} + +static struct i2c_driver max16601_driver = { + .driver = { + .name = "max16601", + }, + .probe_new = max16601_probe, + .id_table = max16601_id, +}; + +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 new file mode 100644 index 000000000..ba39f03c6 --- /dev/null +++ b/drivers/hwmon/pmbus/max20730.c @@ -0,0 +1,788 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for MAX20710, MAX20730, MAX20734, and MAX20743 Integrated, + * Step-Down Switching Regulators + * + * Copyright 2019 Google LLC. + * Copyright 2020 Maxim Integrated + */ + +#include <linux/bits.h> +#include <linux/debugfs.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of_device.h> +#include <linux/pmbus.h> +#include <linux/util_macros.h> +#include "pmbus.h" + +enum chips { + max20710, + max20730, + max20734, + max20743 +}; + +enum { + MAX20730_DEBUGFS_VOUT_MIN = 0, + MAX20730_DEBUGFS_FREQUENCY, + MAX20730_DEBUGFS_PG_DELAY, + MAX20730_DEBUGFS_INTERNAL_GAIN, + MAX20730_DEBUGFS_BOOT_VOLTAGE, + MAX20730_DEBUGFS_OUT_V_RAMP_RATE, + MAX20730_DEBUGFS_OC_PROTECT_MODE, + MAX20730_DEBUGFS_SS_TIMING, + MAX20730_DEBUGFS_IMAX, + MAX20730_DEBUGFS_OPERATION, + MAX20730_DEBUGFS_ON_OFF_CONFIG, + MAX20730_DEBUGFS_SMBALERT_MASK, + MAX20730_DEBUGFS_VOUT_MODE, + MAX20730_DEBUGFS_VOUT_COMMAND, + MAX20730_DEBUGFS_VOUT_MAX, + MAX20730_DEBUGFS_NUM_ENTRIES +}; + +struct max20730_data { + enum chips id; + struct pmbus_driver_info info; + struct mutex lock; /* Used to protect against parallel writes */ + u16 mfr_devset1; + u16 mfr_devset2; + u16 mfr_voutmin; + u32 vout_voltage_divider[2]; +}; + +#define to_max20730_data(x) container_of(x, struct max20730_data, info) + +#define VOLT_FROM_REG(val) DIV_ROUND_CLOSEST((val), 1 << 9) + +#define PMBUS_SMB_ALERT_MASK 0x1B + +#define MAX20730_MFR_VOUT_MIN 0xd1 +#define MAX20730_MFR_DEVSET1 0xd2 +#define MAX20730_MFR_DEVSET2 0xd3 + +#define MAX20730_MFR_VOUT_MIN_MASK GENMASK(9, 0) +#define MAX20730_MFR_VOUT_MIN_BIT_POS 0 + +#define MAX20730_MFR_DEVSET1_RGAIN_MASK (BIT(13) | BIT(14)) +#define MAX20730_MFR_DEVSET1_OTP_MASK (BIT(11) | BIT(12)) +#define MAX20730_MFR_DEVSET1_VBOOT_MASK (BIT(8) | BIT(9)) +#define MAX20730_MFR_DEVSET1_OCP_MASK (BIT(5) | BIT(6)) +#define MAX20730_MFR_DEVSET1_FSW_MASK GENMASK(4, 2) +#define MAX20730_MFR_DEVSET1_TSTAT_MASK (BIT(0) | BIT(1)) + +#define MAX20730_MFR_DEVSET1_RGAIN_BIT_POS 13 +#define MAX20730_MFR_DEVSET1_OTP_BIT_POS 11 +#define MAX20730_MFR_DEVSET1_VBOOT_BIT_POS 8 +#define MAX20730_MFR_DEVSET1_OCP_BIT_POS 5 +#define MAX20730_MFR_DEVSET1_FSW_BIT_POS 2 +#define MAX20730_MFR_DEVSET1_TSTAT_BIT_POS 0 + +#define MAX20730_MFR_DEVSET2_IMAX_MASK GENMASK(10, 8) +#define MAX20730_MFR_DEVSET2_VRATE (BIT(6) | BIT(7)) +#define MAX20730_MFR_DEVSET2_OCPM_MASK BIT(5) +#define MAX20730_MFR_DEVSET2_SS_MASK (BIT(0) | BIT(1)) + +#define MAX20730_MFR_DEVSET2_IMAX_BIT_POS 8 +#define MAX20730_MFR_DEVSET2_VRATE_BIT_POS 6 +#define MAX20730_MFR_DEVSET2_OCPM_BIT_POS 5 +#define MAX20730_MFR_DEVSET2_SS_BIT_POS 0 + +#define DEBUG_FS_DATA_MAX 16 + +struct max20730_debugfs_data { + struct i2c_client *client; + int debugfs_entries[MAX20730_DEBUGFS_NUM_ENTRIES]; +}; + +#define to_psu(x, y) container_of((x), \ + struct max20730_debugfs_data, debugfs_entries[(y)]) + +#ifdef CONFIG_DEBUG_FS +static ssize_t max20730_debugfs_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + int ret, len; + int *idxp = file->private_data; + int idx = *idxp; + struct max20730_debugfs_data *psu = to_psu(idxp, idx); + const struct pmbus_driver_info *info; + const struct max20730_data *data; + char tbuf[DEBUG_FS_DATA_MAX] = { 0 }; + u16 val; + + info = pmbus_get_driver_info(psu->client); + data = to_max20730_data(info); + + switch (idx) { + case MAX20730_DEBUGFS_VOUT_MIN: + ret = VOLT_FROM_REG(data->mfr_voutmin * 10000); + len = scnprintf(tbuf, DEBUG_FS_DATA_MAX, "%d.%d\n", + ret / 10000, ret % 10000); + break; + case MAX20730_DEBUGFS_FREQUENCY: + val = (data->mfr_devset1 & MAX20730_MFR_DEVSET1_FSW_MASK) + >> MAX20730_MFR_DEVSET1_FSW_BIT_POS; + + if (val == 0) + ret = 400; + else if (val == 1) + ret = 500; + else if (val == 2 || val == 3) + ret = 600; + else if (val == 4) + ret = 700; + else if (val == 5) + ret = 800; + else + ret = 900; + len = scnprintf(tbuf, DEBUG_FS_DATA_MAX, "%d\n", ret); + break; + case MAX20730_DEBUGFS_PG_DELAY: + val = (data->mfr_devset1 & MAX20730_MFR_DEVSET1_TSTAT_MASK) + >> MAX20730_MFR_DEVSET1_TSTAT_BIT_POS; + + if (val == 0) + len = strlcpy(tbuf, "2000\n", DEBUG_FS_DATA_MAX); + else if (val == 1) + len = strlcpy(tbuf, "125\n", DEBUG_FS_DATA_MAX); + else if (val == 2) + len = strlcpy(tbuf, "62.5\n", DEBUG_FS_DATA_MAX); + else + len = strlcpy(tbuf, "32\n", DEBUG_FS_DATA_MAX); + break; + case MAX20730_DEBUGFS_INTERNAL_GAIN: + val = (data->mfr_devset1 & MAX20730_MFR_DEVSET1_RGAIN_MASK) + >> MAX20730_MFR_DEVSET1_RGAIN_BIT_POS; + + if (data->id == max20734) { + /* AN6209 */ + if (val == 0) + len = strlcpy(tbuf, "0.8\n", DEBUG_FS_DATA_MAX); + else if (val == 1) + len = strlcpy(tbuf, "3.2\n", DEBUG_FS_DATA_MAX); + else if (val == 2) + len = strlcpy(tbuf, "1.6\n", DEBUG_FS_DATA_MAX); + else + len = strlcpy(tbuf, "6.4\n", DEBUG_FS_DATA_MAX); + } else if (data->id == max20730 || data->id == max20710) { + /* AN6042 or AN6140 */ + if (val == 0) + len = strlcpy(tbuf, "0.9\n", DEBUG_FS_DATA_MAX); + else if (val == 1) + len = strlcpy(tbuf, "3.6\n", DEBUG_FS_DATA_MAX); + else if (val == 2) + len = strlcpy(tbuf, "1.8\n", DEBUG_FS_DATA_MAX); + else + len = strlcpy(tbuf, "7.2\n", DEBUG_FS_DATA_MAX); + } else if (data->id == max20743) { + /* AN6042 */ + if (val == 0) + len = strlcpy(tbuf, "0.45\n", DEBUG_FS_DATA_MAX); + else if (val == 1) + len = strlcpy(tbuf, "1.8\n", DEBUG_FS_DATA_MAX); + else if (val == 2) + len = strlcpy(tbuf, "0.9\n", DEBUG_FS_DATA_MAX); + else + len = strlcpy(tbuf, "3.6\n", DEBUG_FS_DATA_MAX); + } else { + len = strlcpy(tbuf, "Not supported\n", DEBUG_FS_DATA_MAX); + } + break; + case MAX20730_DEBUGFS_BOOT_VOLTAGE: + val = (data->mfr_devset1 & MAX20730_MFR_DEVSET1_VBOOT_MASK) + >> MAX20730_MFR_DEVSET1_VBOOT_BIT_POS; + + if (val == 0) + len = strlcpy(tbuf, "0.6484\n", DEBUG_FS_DATA_MAX); + else if (val == 1) + len = strlcpy(tbuf, "0.8984\n", DEBUG_FS_DATA_MAX); + else if (val == 2) + len = strlcpy(tbuf, "1.0\n", DEBUG_FS_DATA_MAX); + else + len = strlcpy(tbuf, "Invalid\n", DEBUG_FS_DATA_MAX); + break; + case MAX20730_DEBUGFS_OUT_V_RAMP_RATE: + val = (data->mfr_devset2 & MAX20730_MFR_DEVSET2_VRATE) + >> MAX20730_MFR_DEVSET2_VRATE_BIT_POS; + + if (val == 0) + len = strlcpy(tbuf, "4\n", DEBUG_FS_DATA_MAX); + else if (val == 1) + len = strlcpy(tbuf, "2\n", DEBUG_FS_DATA_MAX); + else if (val == 2) + len = strlcpy(tbuf, "1\n", DEBUG_FS_DATA_MAX); + else + len = strlcpy(tbuf, "Invalid\n", DEBUG_FS_DATA_MAX); + break; + case MAX20730_DEBUGFS_OC_PROTECT_MODE: + ret = (data->mfr_devset2 & MAX20730_MFR_DEVSET2_OCPM_MASK) + >> MAX20730_MFR_DEVSET2_OCPM_BIT_POS; + len = scnprintf(tbuf, DEBUG_FS_DATA_MAX, "%d\n", ret); + break; + case MAX20730_DEBUGFS_SS_TIMING: + val = (data->mfr_devset2 & MAX20730_MFR_DEVSET2_SS_MASK) + >> MAX20730_MFR_DEVSET2_SS_BIT_POS; + + if (val == 0) + len = strlcpy(tbuf, "0.75\n", DEBUG_FS_DATA_MAX); + else if (val == 1) + len = strlcpy(tbuf, "1.5\n", DEBUG_FS_DATA_MAX); + else if (val == 2) + len = strlcpy(tbuf, "3\n", DEBUG_FS_DATA_MAX); + else + len = strlcpy(tbuf, "6\n", DEBUG_FS_DATA_MAX); + break; + case MAX20730_DEBUGFS_IMAX: + ret = (data->mfr_devset2 & MAX20730_MFR_DEVSET2_IMAX_MASK) + >> MAX20730_MFR_DEVSET2_IMAX_BIT_POS; + len = scnprintf(tbuf, DEBUG_FS_DATA_MAX, "%d\n", ret); + break; + case MAX20730_DEBUGFS_OPERATION: + ret = i2c_smbus_read_byte_data(psu->client, PMBUS_OPERATION); + if (ret < 0) + return ret; + len = scnprintf(tbuf, DEBUG_FS_DATA_MAX, "%d\n", ret); + break; + case MAX20730_DEBUGFS_ON_OFF_CONFIG: + ret = i2c_smbus_read_byte_data(psu->client, PMBUS_ON_OFF_CONFIG); + if (ret < 0) + return ret; + len = scnprintf(tbuf, DEBUG_FS_DATA_MAX, "%d\n", ret); + break; + case MAX20730_DEBUGFS_SMBALERT_MASK: + ret = i2c_smbus_read_word_data(psu->client, + PMBUS_SMB_ALERT_MASK); + if (ret < 0) + return ret; + len = scnprintf(tbuf, DEBUG_FS_DATA_MAX, "%d\n", ret); + break; + case MAX20730_DEBUGFS_VOUT_MODE: + ret = i2c_smbus_read_byte_data(psu->client, PMBUS_VOUT_MODE); + if (ret < 0) + return ret; + len = scnprintf(tbuf, DEBUG_FS_DATA_MAX, "%d\n", ret); + break; + case MAX20730_DEBUGFS_VOUT_COMMAND: + ret = i2c_smbus_read_word_data(psu->client, PMBUS_VOUT_COMMAND); + if (ret < 0) + return ret; + + ret = VOLT_FROM_REG(ret * 10000); + len = scnprintf(tbuf, DEBUG_FS_DATA_MAX, + "%d.%d\n", ret / 10000, ret % 10000); + break; + case MAX20730_DEBUGFS_VOUT_MAX: + ret = i2c_smbus_read_word_data(psu->client, PMBUS_VOUT_MAX); + if (ret < 0) + return ret; + + ret = VOLT_FROM_REG(ret * 10000); + len = scnprintf(tbuf, DEBUG_FS_DATA_MAX, + "%d.%d\n", ret / 10000, ret % 10000); + break; + default: + len = strlcpy(tbuf, "Invalid\n", DEBUG_FS_DATA_MAX); + } + + return simple_read_from_buffer(buf, count, ppos, tbuf, len); +} + +static const struct file_operations max20730_fops = { + .llseek = noop_llseek, + .read = max20730_debugfs_read, + .write = NULL, + .open = simple_open, +}; + +static int max20730_init_debugfs(struct i2c_client *client, + struct max20730_data *data) +{ + int ret, i; + struct dentry *debugfs; + struct dentry *max20730_dir; + struct max20730_debugfs_data *psu; + + ret = i2c_smbus_read_word_data(client, MAX20730_MFR_DEVSET2); + if (ret < 0) + return ret; + data->mfr_devset2 = ret; + + ret = i2c_smbus_read_word_data(client, MAX20730_MFR_VOUT_MIN); + if (ret < 0) + return ret; + data->mfr_voutmin = ret; + + psu = devm_kzalloc(&client->dev, sizeof(*psu), GFP_KERNEL); + if (!psu) + return -ENOMEM; + psu->client = client; + + debugfs = pmbus_get_debugfs_dir(client); + if (!debugfs) + return -ENOENT; + + max20730_dir = debugfs_create_dir(client->name, debugfs); + + for (i = 0; i < MAX20730_DEBUGFS_NUM_ENTRIES; ++i) + psu->debugfs_entries[i] = i; + + debugfs_create_file("vout_min", 0444, max20730_dir, + &psu->debugfs_entries[MAX20730_DEBUGFS_VOUT_MIN], + &max20730_fops); + debugfs_create_file("frequency", 0444, max20730_dir, + &psu->debugfs_entries[MAX20730_DEBUGFS_FREQUENCY], + &max20730_fops); + debugfs_create_file("power_good_delay", 0444, max20730_dir, + &psu->debugfs_entries[MAX20730_DEBUGFS_PG_DELAY], + &max20730_fops); + debugfs_create_file("internal_gain", 0444, max20730_dir, + &psu->debugfs_entries[MAX20730_DEBUGFS_INTERNAL_GAIN], + &max20730_fops); + debugfs_create_file("boot_voltage", 0444, max20730_dir, + &psu->debugfs_entries[MAX20730_DEBUGFS_BOOT_VOLTAGE], + &max20730_fops); + debugfs_create_file("out_voltage_ramp_rate", 0444, max20730_dir, + &psu->debugfs_entries[MAX20730_DEBUGFS_OUT_V_RAMP_RATE], + &max20730_fops); + debugfs_create_file("oc_protection_mode", 0444, max20730_dir, + &psu->debugfs_entries[MAX20730_DEBUGFS_OC_PROTECT_MODE], + &max20730_fops); + debugfs_create_file("soft_start_timing", 0444, max20730_dir, + &psu->debugfs_entries[MAX20730_DEBUGFS_SS_TIMING], + &max20730_fops); + debugfs_create_file("imax", 0444, max20730_dir, + &psu->debugfs_entries[MAX20730_DEBUGFS_IMAX], + &max20730_fops); + debugfs_create_file("operation", 0444, max20730_dir, + &psu->debugfs_entries[MAX20730_DEBUGFS_OPERATION], + &max20730_fops); + debugfs_create_file("on_off_config", 0444, max20730_dir, + &psu->debugfs_entries[MAX20730_DEBUGFS_ON_OFF_CONFIG], + &max20730_fops); + debugfs_create_file("smbalert_mask", 0444, max20730_dir, + &psu->debugfs_entries[MAX20730_DEBUGFS_SMBALERT_MASK], + &max20730_fops); + debugfs_create_file("vout_mode", 0444, max20730_dir, + &psu->debugfs_entries[MAX20730_DEBUGFS_VOUT_MODE], + &max20730_fops); + debugfs_create_file("vout_command", 0444, max20730_dir, + &psu->debugfs_entries[MAX20730_DEBUGFS_VOUT_COMMAND], + &max20730_fops); + debugfs_create_file("vout_max", 0444, max20730_dir, + &psu->debugfs_entries[MAX20730_DEBUGFS_VOUT_MAX], + &max20730_fops); + + return 0; +} +#else +static int max20730_init_debugfs(struct i2c_client *client, + struct max20730_data *data) +{ + return 0; +} +#endif /* CONFIG_DEBUG_FS */ + +static const struct i2c_device_id max20730_id[]; + +/* + * Convert discreet value to direct data format. Strictly speaking, all passed + * values are constants, so we could do that calculation manually. On the + * downside, that would make the driver more difficult to maintain, so lets + * use this approach. + */ +static u16 val_to_direct(int v, enum pmbus_sensor_classes class, + const struct pmbus_driver_info *info) +{ + int R = info->R[class] - 3; /* take milli-units into account */ + int b = info->b[class] * 1000; + long d; + + d = v * info->m[class] + b; + /* + * R < 0 is true for all callers, so we don't need to bother + * about the R > 0 case. + */ + while (R < 0) { + d = DIV_ROUND_CLOSEST(d, 10); + R++; + } + return (u16)d; +} + +static long direct_to_val(u16 w, enum pmbus_sensor_classes class, + const struct pmbus_driver_info *info) +{ + int R = info->R[class] - 3; + int b = info->b[class] * 1000; + int m = info->m[class]; + long d = (s16)w; + + if (m == 0) + return 0; + + while (R < 0) { + d *= 10; + R++; + } + d = (d - b) / m; + return d; +} + +static u32 max_current[][5] = { + [max20710] = { 6200, 8000, 9700, 11600 }, + [max20730] = { 13000, 16600, 20100, 23600 }, + [max20734] = { 21000, 27000, 32000, 38000 }, + [max20743] = { 18900, 24100, 29200, 34100 }, +}; + +static int max20730_read_word_data(struct i2c_client *client, int page, + int phase, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + const struct max20730_data *data = to_max20730_data(info); + int ret = 0; + u32 max_c; + + switch (reg) { + case PMBUS_OT_FAULT_LIMIT: + switch ((data->mfr_devset1 >> 11) & 0x3) { + case 0x0: + ret = val_to_direct(150000, PSC_TEMPERATURE, info); + break; + case 0x1: + ret = val_to_direct(130000, PSC_TEMPERATURE, info); + break; + default: + ret = -ENODATA; + break; + } + break; + case PMBUS_IOUT_OC_FAULT_LIMIT: + max_c = max_current[data->id][(data->mfr_devset1 >> 5) & 0x3]; + ret = val_to_direct(max_c, PSC_CURRENT_OUT, info); + break; + case PMBUS_READ_VOUT: + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret > 0 && data->vout_voltage_divider[0] && data->vout_voltage_divider[1]) { + u64 temp = DIV_ROUND_CLOSEST_ULL((u64)ret * data->vout_voltage_divider[1], + data->vout_voltage_divider[0]); + ret = clamp_val(temp, 0, 0xffff); + } + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int max20730_write_word_data(struct i2c_client *client, int page, + int reg, u16 word) +{ + struct pmbus_driver_info *info; + struct max20730_data *data; + u16 devset1; + int ret = 0; + int idx; + + info = (struct pmbus_driver_info *)pmbus_get_driver_info(client); + data = to_max20730_data(info); + + mutex_lock(&data->lock); + devset1 = data->mfr_devset1; + + switch (reg) { + case PMBUS_OT_FAULT_LIMIT: + devset1 &= ~(BIT(11) | BIT(12)); + if (direct_to_val(word, PSC_TEMPERATURE, info) < 140000) + devset1 |= BIT(11); + break; + case PMBUS_IOUT_OC_FAULT_LIMIT: + devset1 &= ~(BIT(5) | BIT(6)); + + idx = find_closest(direct_to_val(word, PSC_CURRENT_OUT, info), + max_current[data->id], 4); + devset1 |= (idx << 5); + break; + default: + ret = -ENODATA; + break; + } + + if (!ret && devset1 != data->mfr_devset1) { + ret = i2c_smbus_write_word_data(client, MAX20730_MFR_DEVSET1, + devset1); + if (!ret) { + data->mfr_devset1 = devset1; + pmbus_clear_cache(client); + } + } + mutex_unlock(&data->lock); + return ret; +} + +static const struct pmbus_driver_info max20730_info[] = { + [max20710] = { + .pages = 1, + .read_word_data = max20730_read_word_data, + .write_word_data = max20730_write_word_data, + + /* Source : Maxim AN6140 and AN6042 */ + .format[PSC_TEMPERATURE] = direct, + .m[PSC_TEMPERATURE] = 21, + .b[PSC_TEMPERATURE] = 5887, + .R[PSC_TEMPERATURE] = -1, + + .format[PSC_VOLTAGE_IN] = direct, + .m[PSC_VOLTAGE_IN] = 3609, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = -2, + + .format[PSC_CURRENT_OUT] = direct, + .m[PSC_CURRENT_OUT] = 153, + .b[PSC_CURRENT_OUT] = 4976, + .R[PSC_CURRENT_OUT] = -1, + + .format[PSC_VOLTAGE_OUT] = linear, + + .func[0] = PMBUS_HAVE_VIN | + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_STATUS_INPUT, + }, + [max20730] = { + .pages = 1, + .read_word_data = max20730_read_word_data, + .write_word_data = max20730_write_word_data, + + /* Source : Maxim AN6042 */ + .format[PSC_TEMPERATURE] = direct, + .m[PSC_TEMPERATURE] = 21, + .b[PSC_TEMPERATURE] = 5887, + .R[PSC_TEMPERATURE] = -1, + + .format[PSC_VOLTAGE_IN] = direct, + .m[PSC_VOLTAGE_IN] = 3609, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = -2, + + /* + * Values in the datasheet are adjusted for temperature and + * for the relationship between Vin and Vout. + * Unfortunately, the data sheet suggests that Vout measurement + * may be scaled with a resistor array. This is indeed the case + * at least on the evaulation boards. As a result, any in-driver + * adjustments would either be wrong or require elaborate means + * to configure the scaling. Instead of doing that, just report + * raw values and let userspace handle adjustments. + */ + .format[PSC_CURRENT_OUT] = direct, + .m[PSC_CURRENT_OUT] = 153, + .b[PSC_CURRENT_OUT] = 4976, + .R[PSC_CURRENT_OUT] = -1, + + .format[PSC_VOLTAGE_OUT] = linear, + + .func[0] = PMBUS_HAVE_VIN | + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_STATUS_INPUT, + }, + [max20734] = { + .pages = 1, + .read_word_data = max20730_read_word_data, + .write_word_data = max20730_write_word_data, + + /* Source : Maxim AN6209 */ + .format[PSC_TEMPERATURE] = direct, + .m[PSC_TEMPERATURE] = 21, + .b[PSC_TEMPERATURE] = 5887, + .R[PSC_TEMPERATURE] = -1, + + .format[PSC_VOLTAGE_IN] = direct, + .m[PSC_VOLTAGE_IN] = 3592, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = -2, + + .format[PSC_CURRENT_OUT] = direct, + .m[PSC_CURRENT_OUT] = 111, + .b[PSC_CURRENT_OUT] = 3461, + .R[PSC_CURRENT_OUT] = -1, + + .format[PSC_VOLTAGE_OUT] = linear, + + .func[0] = PMBUS_HAVE_VIN | + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_STATUS_INPUT, + }, + [max20743] = { + .pages = 1, + .read_word_data = max20730_read_word_data, + .write_word_data = max20730_write_word_data, + + /* Source : Maxim AN6042 */ + .format[PSC_TEMPERATURE] = direct, + .m[PSC_TEMPERATURE] = 21, + .b[PSC_TEMPERATURE] = 5887, + .R[PSC_TEMPERATURE] = -1, + + .format[PSC_VOLTAGE_IN] = direct, + .m[PSC_VOLTAGE_IN] = 3597, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = -2, + + .format[PSC_CURRENT_OUT] = direct, + .m[PSC_CURRENT_OUT] = 95, + .b[PSC_CURRENT_OUT] = 5014, + .R[PSC_CURRENT_OUT] = -1, + + .format[PSC_VOLTAGE_OUT] = linear, + + .func[0] = PMBUS_HAVE_VIN | + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_STATUS_INPUT, + }, +}; + +static int max20730_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + u8 buf[I2C_SMBUS_BLOCK_MAX + 1]; + struct max20730_data *data; + enum chips chip_id; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_BYTE_DATA | + I2C_FUNC_SMBUS_READ_WORD_DATA | + I2C_FUNC_SMBUS_BLOCK_DATA)) + return -ENODEV; + + ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf); + if (ret < 0) { + dev_err(&client->dev, "Failed to read Manufacturer ID\n"); + return ret; + } + if (ret != 5 || strncmp(buf, "MAXIM", 5)) { + buf[ret] = '\0'; + dev_err(dev, "Unsupported Manufacturer ID '%s'\n", buf); + return -ENODEV; + } + + /* + * The chips support reading PMBUS_MFR_MODEL. On both MAX20730 + * and MAX20734, reading it returns M20743. Presumably that is + * the reason why the command is not documented. Unfortunately, + * that means that there is no reliable means to detect the chip. + * However, we can at least detect the chip series. Compare + * the returned value against 'M20743' and bail out if there is + * a mismatch. If that doesn't work for all chips, we may have + * to remove this check. + */ + 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 (ret != 6 || strncmp(buf, "M20743", 6)) { + buf[ret] = '\0'; + dev_err(dev, "Unsupported Manufacturer Model '%s'\n", buf); + return -ENODEV; + } + + ret = i2c_smbus_read_block_data(client, PMBUS_MFR_REVISION, buf); + if (ret < 0) { + dev_err(dev, "Failed to read Manufacturer Revision\n"); + return ret; + } + if (ret != 1 || buf[0] != 'F') { + buf[ret] = '\0'; + dev_err(dev, "Unsupported Manufacturer Revision '%s'\n", buf); + return -ENODEV; + } + + if (client->dev.of_node) + chip_id = (enum chips)of_device_get_match_data(dev); + else + chip_id = i2c_match_id(max20730_id, client)->driver_data; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + data->id = chip_id; + mutex_init(&data->lock); + memcpy(&data->info, &max20730_info[chip_id], sizeof(data->info)); + if (of_property_read_u32_array(client->dev.of_node, "vout-voltage-divider", + data->vout_voltage_divider, + ARRAY_SIZE(data->vout_voltage_divider)) != 0) + memset(data->vout_voltage_divider, 0, sizeof(data->vout_voltage_divider)); + if (data->vout_voltage_divider[1] < data->vout_voltage_divider[0]) { + dev_err(dev, + "The total resistance of voltage divider is less than output resistance\n"); + return -EINVAL; + } + + ret = i2c_smbus_read_word_data(client, MAX20730_MFR_DEVSET1); + if (ret < 0) + return ret; + data->mfr_devset1 = ret; + + ret = pmbus_do_probe(client, &data->info); + if (ret < 0) + return ret; + + ret = max20730_init_debugfs(client, data); + if (ret) + dev_warn(dev, "Failed to register debugfs: %d\n", + ret); + + return 0; +} + +static const struct i2c_device_id max20730_id[] = { + { "max20710", max20710 }, + { "max20730", max20730 }, + { "max20734", max20734 }, + { "max20743", max20743 }, + { }, +}; + +MODULE_DEVICE_TABLE(i2c, max20730_id); + +static const struct of_device_id max20730_of_match[] = { + { .compatible = "maxim,max20710", .data = (void *)max20710 }, + { .compatible = "maxim,max20730", .data = (void *)max20730 }, + { .compatible = "maxim,max20734", .data = (void *)max20734 }, + { .compatible = "maxim,max20743", .data = (void *)max20743 }, + { }, +}; + +MODULE_DEVICE_TABLE(of, max20730_of_match); + +static struct i2c_driver max20730_driver = { + .driver = { + .name = "max20730", + .of_match_table = max20730_of_match, + }, + .probe_new = max20730_probe, + .id_table = max20730_id, +}; + +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 new file mode 100644 index 000000000..2272dc8c2 --- /dev/null +++ b/drivers/hwmon/pmbus/max20751.c @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for Maxim MAX20751 + * + * Copyright (c) 2015 Guenter Roeck + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include "pmbus.h" + +static struct pmbus_driver_info max20751_info = { + .pages = 1, + .format[PSC_VOLTAGE_IN] = linear, + .format[PSC_VOLTAGE_OUT] = vid, + .vrm_version[0] = vr12, + .format[PSC_TEMPERATURE] = linear, + .format[PSC_CURRENT_OUT] = linear, + .format[PSC_POWER] = linear, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_POUT, +}; + +static int max20751_probe(struct i2c_client *client) +{ + return pmbus_do_probe(client, &max20751_info); +} + +static const struct i2c_device_id max20751_id[] = { + {"max20751", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, max20751_id); + +static struct i2c_driver max20751_driver = { + .driver = { + .name = "max20751", + }, + .probe_new = max20751_probe, + .id_table = max20751_id, +}; + +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 new file mode 100644 index 000000000..95d79a64b --- /dev/null +++ b/drivers/hwmon/pmbus/max31785.c @@ -0,0 +1,406 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2017 IBM Corp. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include "pmbus.h" + +enum max31785_regs { + MFR_REVISION = 0x9b, + MFR_FAN_CONFIG = 0xf1, +}; + +#define MAX31785 0x3030 +#define MAX31785A 0x3040 +#define MAX31785B 0x3061 + +#define MFR_FAN_CONFIG_DUAL_TACH BIT(12) + +#define MAX31785_NR_PAGES 23 +#define MAX31785_NR_FAN_PAGES 6 + +static int max31785_read_byte_data(struct i2c_client *client, int page, + int reg) +{ + if (page < MAX31785_NR_PAGES) + return -ENODATA; + + switch (reg) { + case PMBUS_VOUT_MODE: + return -ENOTSUPP; + case PMBUS_FAN_CONFIG_12: + return pmbus_read_byte_data(client, page - MAX31785_NR_PAGES, + reg); + } + + return -ENODATA; +} + +static int max31785_write_byte(struct i2c_client *client, int page, u8 value) +{ + if (page < MAX31785_NR_PAGES) + return -ENODATA; + + return -ENOTSUPP; +} + +static int max31785_read_long_data(struct i2c_client *client, int page, + int reg, u32 *data) +{ + unsigned char cmdbuf[1]; + unsigned char rspbuf[4]; + int rc; + + struct i2c_msg msg[2] = { + { + .addr = client->addr, + .flags = 0, + .len = sizeof(cmdbuf), + .buf = cmdbuf, + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = sizeof(rspbuf), + .buf = rspbuf, + }, + }; + + cmdbuf[0] = reg; + + rc = pmbus_set_page(client, page, 0xff); + if (rc < 0) + return rc; + + rc = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + if (rc < 0) + return rc; + + *data = (rspbuf[0] << (0 * 8)) | (rspbuf[1] << (1 * 8)) | + (rspbuf[2] << (2 * 8)) | (rspbuf[3] << (3 * 8)); + + return rc; +} + +static int max31785_get_pwm(struct i2c_client *client, int page) +{ + int rv; + + rv = pmbus_get_fan_rate_device(client, page, 0, percent); + if (rv < 0) + return rv; + else if (rv >= 0x8000) + return 0; + else if (rv >= 0x2711) + return 0x2710; + + return rv; +} + +static int max31785_get_pwm_mode(struct i2c_client *client, int page) +{ + int config; + int command; + + config = pmbus_read_byte_data(client, page, PMBUS_FAN_CONFIG_12); + if (config < 0) + return config; + + command = pmbus_read_word_data(client, page, 0xff, PMBUS_FAN_COMMAND_1); + if (command < 0) + return command; + + if (config & PB_FAN_1_RPM) + return (command >= 0x8000) ? 3 : 2; + + if (command >= 0x8000) + return 3; + else if (command >= 0x2711) + return 0; + + return 1; +} + +static int max31785_read_word_data(struct i2c_client *client, int page, + int phase, int reg) +{ + u32 val; + int rv; + + switch (reg) { + case PMBUS_READ_FAN_SPEED_1: + if (page < MAX31785_NR_PAGES) + return -ENODATA; + + rv = max31785_read_long_data(client, page - MAX31785_NR_PAGES, + reg, &val); + if (rv < 0) + return rv; + + rv = (val >> 16) & 0xffff; + break; + case PMBUS_FAN_COMMAND_1: + /* + * PMBUS_FAN_COMMAND_x is probed to judge whether or not to + * expose fan control registers. + * + * Don't expose fan_target attribute for virtual pages. + */ + rv = (page >= MAX31785_NR_PAGES) ? -ENOTSUPP : -ENODATA; + break; + case PMBUS_VIRT_PWM_1: + rv = max31785_get_pwm(client, page); + break; + case PMBUS_VIRT_PWM_ENABLE_1: + rv = max31785_get_pwm_mode(client, page); + break; + default: + rv = -ENODATA; + break; + } + + return rv; +} + +static inline u32 max31785_scale_pwm(u32 sensor_val) +{ + /* + * The datasheet describes the accepted value range for manual PWM as + * [0, 0x2710], while the hwmon pwmX sysfs interface accepts values in + * [0, 255]. The MAX31785 uses DIRECT mode to scale the FAN_COMMAND + * registers and in PWM mode the coefficients are m=1, b=0, R=2. The + * important observation here is that 0x2710 == 10000 == 100 * 100. + * + * R=2 (== 10^2 == 100) accounts for scaling the value provided at the + * sysfs interface into the required hardware resolution, but it does + * not yet yield a value that we can write to the device (this initial + * scaling is handled by pmbus_data2reg()). Multiplying by 100 below + * translates the parameter value into the percentage units required by + * PMBus, and then we scale back by 255 as required by the hwmon pwmX + * interface to yield the percentage value at the appropriate + * resolution for hardware. + */ + return (sensor_val * 100) / 255; +} + +static int max31785_pwm_enable(struct i2c_client *client, int page, + u16 word) +{ + int config = 0; + int rate; + + switch (word) { + case 0: + rate = 0x7fff; + break; + case 1: + rate = pmbus_get_fan_rate_cached(client, page, 0, percent); + if (rate < 0) + return rate; + rate = max31785_scale_pwm(rate); + break; + case 2: + config = PB_FAN_1_RPM; + rate = pmbus_get_fan_rate_cached(client, page, 0, rpm); + if (rate < 0) + return rate; + break; + case 3: + rate = 0xffff; + break; + default: + return -EINVAL; + } + + return pmbus_update_fan(client, page, 0, config, PB_FAN_1_RPM, rate); +} + +static int max31785_write_word_data(struct i2c_client *client, int page, + int reg, u16 word) +{ + switch (reg) { + case PMBUS_VIRT_PWM_1: + return pmbus_update_fan(client, page, 0, 0, PB_FAN_1_RPM, + max31785_scale_pwm(word)); + case PMBUS_VIRT_PWM_ENABLE_1: + return max31785_pwm_enable(client, page, word); + default: + break; + } + + return -ENODATA; +} + +#define MAX31785_FAN_FUNCS \ + (PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12 | PMBUS_HAVE_PWM12) + +#define MAX31785_TEMP_FUNCS \ + (PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP) + +#define MAX31785_VOUT_FUNCS \ + (PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT) + +static const struct pmbus_driver_info max31785_info = { + .pages = MAX31785_NR_PAGES, + + .write_word_data = max31785_write_word_data, + .read_byte_data = max31785_read_byte_data, + .read_word_data = max31785_read_word_data, + .write_byte = max31785_write_byte, + + /* RPM */ + .format[PSC_FAN] = direct, + .m[PSC_FAN] = 1, + .b[PSC_FAN] = 0, + .R[PSC_FAN] = 0, + /* PWM */ + .format[PSC_PWM] = direct, + .m[PSC_PWM] = 1, + .b[PSC_PWM] = 0, + .R[PSC_PWM] = 2, + .func[0] = MAX31785_FAN_FUNCS, + .func[1] = MAX31785_FAN_FUNCS, + .func[2] = MAX31785_FAN_FUNCS, + .func[3] = MAX31785_FAN_FUNCS, + .func[4] = MAX31785_FAN_FUNCS, + .func[5] = MAX31785_FAN_FUNCS, + + .format[PSC_TEMPERATURE] = direct, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 2, + .func[6] = MAX31785_TEMP_FUNCS, + .func[7] = MAX31785_TEMP_FUNCS, + .func[8] = MAX31785_TEMP_FUNCS, + .func[9] = MAX31785_TEMP_FUNCS, + .func[10] = MAX31785_TEMP_FUNCS, + .func[11] = MAX31785_TEMP_FUNCS, + .func[12] = MAX31785_TEMP_FUNCS, + .func[13] = MAX31785_TEMP_FUNCS, + .func[14] = MAX31785_TEMP_FUNCS, + .func[15] = MAX31785_TEMP_FUNCS, + .func[16] = MAX31785_TEMP_FUNCS, + + .format[PSC_VOLTAGE_OUT] = direct, + .m[PSC_VOLTAGE_OUT] = 1, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 0, + .func[17] = MAX31785_VOUT_FUNCS, + .func[18] = MAX31785_VOUT_FUNCS, + .func[19] = MAX31785_VOUT_FUNCS, + .func[20] = MAX31785_VOUT_FUNCS, + .func[21] = MAX31785_VOUT_FUNCS, + .func[22] = MAX31785_VOUT_FUNCS, +}; + +static int max31785_configure_dual_tach(struct i2c_client *client, + struct pmbus_driver_info *info) +{ + int ret; + int i; + + for (i = 0; i < MAX31785_NR_FAN_PAGES; i++) { + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, i); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_word_data(client, MFR_FAN_CONFIG); + if (ret < 0) + return ret; + + if (ret & MFR_FAN_CONFIG_DUAL_TACH) { + int virtual = MAX31785_NR_PAGES + i; + + info->pages = virtual + 1; + info->func[virtual] |= PMBUS_HAVE_FAN12; + info->func[virtual] |= PMBUS_PAGE_VIRTUAL; + } + } + + return 0; +} + +static int max31785_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct pmbus_driver_info *info; + bool dual_tach = false; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + + info = devm_kzalloc(dev, sizeof(struct pmbus_driver_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + *info = max31785_info; + + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 255); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_word_data(client, MFR_REVISION); + if (ret < 0) + return ret; + + if (ret == MAX31785A || ret == MAX31785B) { + dual_tach = true; + } else if (ret == MAX31785) { + if (!strcmp("max31785a", client->name) || + !strcmp("max31785b", client->name)) + dev_warn(dev, "Expected max31785a/b, found max31785: cannot provide secondary tachometer readings\n"); + } else { + dev_err(dev, "Unrecognized MAX31785 revision: %x\n", ret); + return -ENODEV; + } + + if (dual_tach) { + ret = max31785_configure_dual_tach(client, info); + if (ret < 0) + return ret; + } + + return pmbus_do_probe(client, info); +} + +static const struct i2c_device_id max31785_id[] = { + { "max31785", 0 }, + { "max31785a", 0 }, + { "max31785b", 0 }, + { }, +}; + +MODULE_DEVICE_TABLE(i2c, max31785_id); + +static const struct of_device_id max31785_of_match[] = { + { .compatible = "maxim,max31785" }, + { .compatible = "maxim,max31785a" }, + { .compatible = "maxim,max31785b" }, + { }, +}; + +MODULE_DEVICE_TABLE(of, max31785_of_match); + +static struct i2c_driver max31785_driver = { + .driver = { + .name = "max31785", + .of_match_table = max31785_of_match, + }, + .probe_new = max31785_probe, + .id_table = max31785_id, +}; + +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 new file mode 100644 index 000000000..ea7609058 --- /dev/null +++ b/drivers/hwmon/pmbus/max34440.c @@ -0,0 +1,532 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for Maxim MAX34440/MAX34441 + * + * Copyright (c) 2011 Ericsson AB. + * Copyright (c) 2012 Guenter Roeck + */ + +#include <linux/bitops.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include "pmbus.h" + +enum chips { max34440, max34441, max34446, max34451, max34460, max34461 }; + +#define MAX34440_MFR_VOUT_PEAK 0xd4 +#define MAX34440_MFR_IOUT_PEAK 0xd5 +#define MAX34440_MFR_TEMPERATURE_PEAK 0xd6 +#define MAX34440_MFR_VOUT_MIN 0xd7 + +#define MAX34446_MFR_POUT_PEAK 0xe0 +#define MAX34446_MFR_POUT_AVG 0xe1 +#define MAX34446_MFR_IOUT_AVG 0xe2 +#define MAX34446_MFR_TEMPERATURE_AVG 0xe3 + +#define MAX34440_STATUS_OC_WARN BIT(0) +#define MAX34440_STATUS_OC_FAULT BIT(1) +#define MAX34440_STATUS_OT_FAULT BIT(5) +#define MAX34440_STATUS_OT_WARN BIT(6) + +/* + * The whole max344* family have IOUT_OC_WARN_LIMIT and IOUT_OC_FAULT_LIMIT + * swapped from the standard pmbus spec addresses. + */ +#define MAX34440_IOUT_OC_WARN_LIMIT 0x46 +#define MAX34440_IOUT_OC_FAULT_LIMIT 0x4A + +#define MAX34451_MFR_CHANNEL_CONFIG 0xe4 +#define MAX34451_MFR_CHANNEL_CONFIG_SEL_MASK 0x3f + +struct max34440_data { + int id; + struct pmbus_driver_info info; +}; + +#define to_max34440_data(x) container_of(x, struct max34440_data, info) + +static const struct i2c_device_id max34440_id[]; + +static int max34440_read_word_data(struct i2c_client *client, int page, + int phase, int reg) +{ + int ret; + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + const struct max34440_data *data = to_max34440_data(info); + + switch (reg) { + case PMBUS_IOUT_OC_FAULT_LIMIT: + ret = pmbus_read_word_data(client, page, phase, + MAX34440_IOUT_OC_FAULT_LIMIT); + break; + case PMBUS_IOUT_OC_WARN_LIMIT: + ret = pmbus_read_word_data(client, page, phase, + MAX34440_IOUT_OC_WARN_LIMIT); + break; + case PMBUS_VIRT_READ_VOUT_MIN: + ret = pmbus_read_word_data(client, page, phase, + MAX34440_MFR_VOUT_MIN); + break; + case PMBUS_VIRT_READ_VOUT_MAX: + ret = pmbus_read_word_data(client, page, phase, + MAX34440_MFR_VOUT_PEAK); + break; + case PMBUS_VIRT_READ_IOUT_AVG: + if (data->id != max34446 && data->id != max34451) + return -ENXIO; + ret = pmbus_read_word_data(client, page, phase, + MAX34446_MFR_IOUT_AVG); + break; + case PMBUS_VIRT_READ_IOUT_MAX: + ret = pmbus_read_word_data(client, page, phase, + MAX34440_MFR_IOUT_PEAK); + break; + case PMBUS_VIRT_READ_POUT_AVG: + if (data->id != max34446) + return -ENXIO; + ret = pmbus_read_word_data(client, page, phase, + MAX34446_MFR_POUT_AVG); + break; + case PMBUS_VIRT_READ_POUT_MAX: + if (data->id != max34446) + return -ENXIO; + ret = pmbus_read_word_data(client, page, phase, + MAX34446_MFR_POUT_PEAK); + break; + case PMBUS_VIRT_READ_TEMP_AVG: + if (data->id != max34446 && data->id != max34460 && + data->id != max34461) + return -ENXIO; + ret = pmbus_read_word_data(client, page, phase, + MAX34446_MFR_TEMPERATURE_AVG); + break; + case PMBUS_VIRT_READ_TEMP_MAX: + ret = pmbus_read_word_data(client, page, phase, + MAX34440_MFR_TEMPERATURE_PEAK); + break; + case PMBUS_VIRT_RESET_POUT_HISTORY: + if (data->id != max34446) + return -ENXIO; + ret = 0; + break; + case PMBUS_VIRT_RESET_VOUT_HISTORY: + case PMBUS_VIRT_RESET_IOUT_HISTORY: + case PMBUS_VIRT_RESET_TEMP_HISTORY: + ret = 0; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int max34440_write_word_data(struct i2c_client *client, int page, + int reg, u16 word) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + const struct max34440_data *data = to_max34440_data(info); + int ret; + + switch (reg) { + case PMBUS_IOUT_OC_FAULT_LIMIT: + ret = pmbus_write_word_data(client, page, MAX34440_IOUT_OC_FAULT_LIMIT, + word); + break; + case PMBUS_IOUT_OC_WARN_LIMIT: + ret = pmbus_write_word_data(client, page, MAX34440_IOUT_OC_WARN_LIMIT, + word); + break; + case PMBUS_VIRT_RESET_POUT_HISTORY: + ret = pmbus_write_word_data(client, page, + MAX34446_MFR_POUT_PEAK, 0); + if (ret) + break; + ret = pmbus_write_word_data(client, page, + MAX34446_MFR_POUT_AVG, 0); + break; + case PMBUS_VIRT_RESET_VOUT_HISTORY: + ret = pmbus_write_word_data(client, page, + MAX34440_MFR_VOUT_MIN, 0x7fff); + if (ret) + break; + ret = pmbus_write_word_data(client, page, + MAX34440_MFR_VOUT_PEAK, 0); + break; + case PMBUS_VIRT_RESET_IOUT_HISTORY: + ret = pmbus_write_word_data(client, page, + MAX34440_MFR_IOUT_PEAK, 0); + if (!ret && (data->id == max34446 || data->id == max34451)) + ret = pmbus_write_word_data(client, page, + MAX34446_MFR_IOUT_AVG, 0); + + break; + case PMBUS_VIRT_RESET_TEMP_HISTORY: + ret = pmbus_write_word_data(client, page, + MAX34440_MFR_TEMPERATURE_PEAK, + 0x8000); + if (!ret && data->id == max34446) + ret = pmbus_write_word_data(client, page, + MAX34446_MFR_TEMPERATURE_AVG, 0); + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int max34440_read_byte_data(struct i2c_client *client, int page, int reg) +{ + int ret = 0; + int mfg_status; + + if (page >= 0) { + ret = pmbus_set_page(client, page, 0xff); + if (ret < 0) + return ret; + } + + switch (reg) { + case PMBUS_STATUS_IOUT: + mfg_status = pmbus_read_word_data(client, 0, 0xff, + PMBUS_STATUS_MFR_SPECIFIC); + if (mfg_status < 0) + return mfg_status; + if (mfg_status & MAX34440_STATUS_OC_WARN) + ret |= PB_IOUT_OC_WARNING; + if (mfg_status & MAX34440_STATUS_OC_FAULT) + ret |= PB_IOUT_OC_FAULT; + break; + case PMBUS_STATUS_TEMPERATURE: + mfg_status = pmbus_read_word_data(client, 0, 0xff, + PMBUS_STATUS_MFR_SPECIFIC); + if (mfg_status < 0) + return mfg_status; + if (mfg_status & MAX34440_STATUS_OT_WARN) + ret |= PB_TEMP_OT_WARNING; + if (mfg_status & MAX34440_STATUS_OT_FAULT) + ret |= PB_TEMP_OT_FAULT; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int max34451_set_supported_funcs(struct i2c_client *client, + struct max34440_data *data) +{ + /* + * Each of the channel 0-15 can be configured to monitor the following + * functions based on MFR_CHANNEL_CONFIG[5:0] + * 0x10: Sequencing + voltage monitoring (only valid for PAGES 0–11) + * 0x20: Voltage monitoring (no sequencing) + * 0x21: Voltage read only + * 0x22: Current monitoring + * 0x23: Current read only + * 0x30: General-purpose input active low + * 0x34: General-purpose input active high + * 0x00: Disabled + */ + + int page, rv; + + for (page = 0; page < 16; page++) { + rv = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page); + if (rv < 0) + return rv; + + rv = i2c_smbus_read_word_data(client, + MAX34451_MFR_CHANNEL_CONFIG); + if (rv < 0) + return rv; + + switch (rv & MAX34451_MFR_CHANNEL_CONFIG_SEL_MASK) { + case 0x10: + case 0x20: + data->info.func[page] = PMBUS_HAVE_VOUT | + PMBUS_HAVE_STATUS_VOUT; + break; + case 0x21: + data->info.func[page] = PMBUS_HAVE_VOUT; + break; + case 0x22: + data->info.func[page] = PMBUS_HAVE_IOUT | + PMBUS_HAVE_STATUS_IOUT; + break; + case 0x23: + data->info.func[page] = PMBUS_HAVE_IOUT; + break; + default: + break; + } + } + + return 0; +} + +static struct pmbus_driver_info max34440_info[] = { + [max34440] = { + .pages = 14, + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .format[PSC_CURRENT_OUT] = direct, + .m[PSC_VOLTAGE_IN] = 1, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 3, /* R = 0 in datasheet reflects mV */ + .m[PSC_VOLTAGE_OUT] = 1, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 3, /* R = 0 in datasheet reflects mV */ + .m[PSC_CURRENT_OUT] = 1, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = 3, /* R = 0 in datasheet reflects mA */ + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 2, + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[3] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[4] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[5] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[6] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[7] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[8] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[9] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[10] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[11] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[12] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[13] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .read_byte_data = max34440_read_byte_data, + .read_word_data = max34440_read_word_data, + .write_word_data = max34440_write_word_data, + }, + [max34441] = { + .pages = 12, + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .format[PSC_CURRENT_OUT] = direct, + .format[PSC_FAN] = direct, + .m[PSC_VOLTAGE_IN] = 1, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 3, + .m[PSC_VOLTAGE_OUT] = 1, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 3, + .m[PSC_CURRENT_OUT] = 1, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = 3, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 2, + .m[PSC_FAN] = 1, + .b[PSC_FAN] = 0, + .R[PSC_FAN] = 0, + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[3] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[4] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[5] = PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12, + .func[6] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[7] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[8] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[9] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[10] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[11] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .read_byte_data = max34440_read_byte_data, + .read_word_data = max34440_read_word_data, + .write_word_data = max34440_write_word_data, + }, + [max34446] = { + .pages = 7, + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .format[PSC_CURRENT_OUT] = direct, + .format[PSC_POWER] = direct, + .m[PSC_VOLTAGE_IN] = 1, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 3, + .m[PSC_VOLTAGE_OUT] = 1, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 3, + .m[PSC_CURRENT_OUT] = 1, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = 3, + .m[PSC_POWER] = 1, + .b[PSC_POWER] = 0, + .R[PSC_POWER] = 3, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 2, + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_POUT, + .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_POUT, + .func[3] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[4] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[5] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[6] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .read_byte_data = max34440_read_byte_data, + .read_word_data = max34440_read_word_data, + .write_word_data = max34440_write_word_data, + }, + [max34451] = { + .pages = 21, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .format[PSC_CURRENT_OUT] = direct, + .m[PSC_VOLTAGE_OUT] = 1, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 3, + .m[PSC_CURRENT_OUT] = 1, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = 2, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 2, + /* func 0-15 is set dynamically before probing */ + .func[16] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[17] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[18] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[19] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[20] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .read_word_data = max34440_read_word_data, + .write_word_data = max34440_write_word_data, + }, + [max34460] = { + .pages = 18, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .m[PSC_VOLTAGE_OUT] = 1, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 3, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 2, + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[3] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[4] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[5] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[6] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[7] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[8] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[9] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[10] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[11] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[13] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[14] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[15] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[16] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[17] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .read_word_data = max34440_read_word_data, + .write_word_data = max34440_write_word_data, + }, + [max34461] = { + .pages = 23, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .m[PSC_VOLTAGE_OUT] = 1, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 3, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 2, + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[3] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[4] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[5] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[6] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[7] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[8] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[9] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[10] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[11] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[12] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[13] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[14] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[15] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + /* page 16 is reserved */ + .func[17] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[18] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[19] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[20] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[21] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .read_word_data = max34440_read_word_data, + .write_word_data = max34440_write_word_data, + }, +}; + +static int max34440_probe(struct i2c_client *client) +{ + struct max34440_data *data; + int rv; + + data = devm_kzalloc(&client->dev, sizeof(struct max34440_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + data->id = i2c_match_id(max34440_id, client)->driver_data; + data->info = max34440_info[data->id]; + + if (data->id == max34451) { + rv = max34451_set_supported_funcs(client, data); + if (rv) + return rv; + } + + return pmbus_do_probe(client, &data->info); +} + +static const struct i2c_device_id max34440_id[] = { + {"max34440", max34440}, + {"max34441", max34441}, + {"max34446", max34446}, + {"max34451", max34451}, + {"max34460", max34460}, + {"max34461", max34461}, + {} +}; +MODULE_DEVICE_TABLE(i2c, max34440_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver max34440_driver = { + .driver = { + .name = "max34440", + }, + .probe_new = max34440_probe, + .id_table = max34440_id, +}; + +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 new file mode 100644 index 000000000..5e66c28c0 --- /dev/null +++ b/drivers/hwmon/pmbus/max8688.c @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for Maxim MAX8688 + * + * Copyright (c) 2011 Ericsson AB. + */ + +#include <linux/bitops.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include "pmbus.h" + +#define MAX8688_MFR_VOUT_PEAK 0xd4 +#define MAX8688_MFR_IOUT_PEAK 0xd5 +#define MAX8688_MFR_TEMPERATURE_PEAK 0xd6 +#define MAX8688_MFG_STATUS 0xd8 + +#define MAX8688_STATUS_OC_FAULT BIT(4) +#define MAX8688_STATUS_OV_FAULT BIT(5) +#define MAX8688_STATUS_OV_WARNING BIT(8) +#define MAX8688_STATUS_UV_FAULT BIT(9) +#define MAX8688_STATUS_UV_WARNING BIT(10) +#define MAX8688_STATUS_UC_FAULT BIT(11) +#define MAX8688_STATUS_OC_WARNING BIT(12) +#define MAX8688_STATUS_OT_FAULT BIT(13) +#define MAX8688_STATUS_OT_WARNING BIT(14) + +static int max8688_read_word_data(struct i2c_client *client, int page, + int phase, int reg) +{ + int ret; + + if (page > 0) + return -ENXIO; + + switch (reg) { + case PMBUS_VIRT_READ_VOUT_MAX: + ret = pmbus_read_word_data(client, 0, 0xff, + MAX8688_MFR_VOUT_PEAK); + break; + case PMBUS_VIRT_READ_IOUT_MAX: + ret = pmbus_read_word_data(client, 0, 0xff, + MAX8688_MFR_IOUT_PEAK); + break; + case PMBUS_VIRT_READ_TEMP_MAX: + ret = pmbus_read_word_data(client, 0, 0xff, + MAX8688_MFR_TEMPERATURE_PEAK); + break; + case PMBUS_VIRT_RESET_VOUT_HISTORY: + case PMBUS_VIRT_RESET_IOUT_HISTORY: + case PMBUS_VIRT_RESET_TEMP_HISTORY: + ret = 0; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int max8688_write_word_data(struct i2c_client *client, int page, int reg, + u16 word) +{ + int ret; + + switch (reg) { + case PMBUS_VIRT_RESET_VOUT_HISTORY: + ret = pmbus_write_word_data(client, 0, MAX8688_MFR_VOUT_PEAK, + 0); + break; + case PMBUS_VIRT_RESET_IOUT_HISTORY: + ret = pmbus_write_word_data(client, 0, MAX8688_MFR_IOUT_PEAK, + 0); + break; + case PMBUS_VIRT_RESET_TEMP_HISTORY: + ret = pmbus_write_word_data(client, 0, + MAX8688_MFR_TEMPERATURE_PEAK, + 0xffff); + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int max8688_read_byte_data(struct i2c_client *client, int page, int reg) +{ + int ret = 0; + int mfg_status; + + if (page > 0) + return -ENXIO; + + switch (reg) { + case PMBUS_STATUS_VOUT: + mfg_status = pmbus_read_word_data(client, 0, 0xff, + MAX8688_MFG_STATUS); + if (mfg_status < 0) + return mfg_status; + if (mfg_status & MAX8688_STATUS_UV_WARNING) + ret |= PB_VOLTAGE_UV_WARNING; + if (mfg_status & MAX8688_STATUS_UV_FAULT) + ret |= PB_VOLTAGE_UV_FAULT; + if (mfg_status & MAX8688_STATUS_OV_WARNING) + ret |= PB_VOLTAGE_OV_WARNING; + if (mfg_status & MAX8688_STATUS_OV_FAULT) + ret |= PB_VOLTAGE_OV_FAULT; + break; + case PMBUS_STATUS_IOUT: + mfg_status = pmbus_read_word_data(client, 0, 0xff, + MAX8688_MFG_STATUS); + if (mfg_status < 0) + return mfg_status; + if (mfg_status & MAX8688_STATUS_UC_FAULT) + ret |= PB_IOUT_UC_FAULT; + if (mfg_status & MAX8688_STATUS_OC_WARNING) + ret |= PB_IOUT_OC_WARNING; + if (mfg_status & MAX8688_STATUS_OC_FAULT) + ret |= PB_IOUT_OC_FAULT; + break; + case PMBUS_STATUS_TEMPERATURE: + mfg_status = pmbus_read_word_data(client, 0, 0xff, + MAX8688_MFG_STATUS); + if (mfg_status < 0) + return mfg_status; + if (mfg_status & MAX8688_STATUS_OT_WARNING) + ret |= PB_TEMP_OT_WARNING; + if (mfg_status & MAX8688_STATUS_OT_FAULT) + ret |= PB_TEMP_OT_FAULT; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static struct pmbus_driver_info max8688_info = { + .pages = 1, + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .format[PSC_CURRENT_OUT] = direct, + .m[PSC_VOLTAGE_IN] = 19995, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = -1, + .m[PSC_VOLTAGE_OUT] = 19995, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = -1, + .m[PSC_CURRENT_OUT] = 23109, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = -2, + .m[PSC_TEMPERATURE] = -7612, + .b[PSC_TEMPERATURE] = 335, + .R[PSC_TEMPERATURE] = -3, + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP + | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT + | PMBUS_HAVE_STATUS_TEMP, + .read_byte_data = max8688_read_byte_data, + .read_word_data = max8688_read_word_data, + .write_word_data = max8688_write_word_data, +}; + +static int max8688_probe(struct i2c_client *client) +{ + return pmbus_do_probe(client, &max8688_info); +} + +static const struct i2c_device_id max8688_id[] = { + {"max8688", 0}, + { } +}; + +MODULE_DEVICE_TABLE(i2c, max8688_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver max8688_driver = { + .driver = { + .name = "max8688", + }, + .probe_new = max8688_probe, + .id_table = max8688_id, +}; + +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/mp2888.c b/drivers/hwmon/pmbus/mp2888.c new file mode 100644 index 000000000..24e519470 --- /dev/null +++ b/drivers/hwmon/pmbus/mp2888.c @@ -0,0 +1,407 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for MPS Multi-phase Digital VR Controllers + * + * Copyright (C) 2020 Nvidia Technologies Ltd. + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include "pmbus.h" + +/* Vendor specific registers. */ +#define MP2888_MFR_SYS_CONFIG 0x44 +#define MP2888_MFR_READ_CS1_2 0x73 +#define MP2888_MFR_READ_CS3_4 0x74 +#define MP2888_MFR_READ_CS5_6 0x75 +#define MP2888_MFR_READ_CS7_8 0x76 +#define MP2888_MFR_READ_CS9_10 0x77 +#define MP2888_MFR_VR_CONFIG1 0xe1 + +#define MP2888_TOTAL_CURRENT_RESOLUTION BIT(3) +#define MP2888_PHASE_CURRENT_RESOLUTION BIT(4) +#define MP2888_DRMOS_KCS GENMASK(2, 0) +#define MP2888_TEMP_UNIT 10 +#define MP2888_MAX_PHASE 10 + +struct mp2888_data { + struct pmbus_driver_info info; + int total_curr_resolution; + int phase_curr_resolution; + int curr_sense_gain; +}; + +#define to_mp2888_data(x) container_of(x, struct mp2888_data, info) + +static int mp2888_read_byte_data(struct i2c_client *client, int page, int reg) +{ + switch (reg) { + case PMBUS_VOUT_MODE: + /* Enforce VOUT direct format. */ + return PB_VOUT_MODE_DIRECT; + default: + return -ENODATA; + } +} + +static int +mp2888_current_sense_gain_and_resolution_get(struct i2c_client *client, struct mp2888_data *data) +{ + int ret; + + /* + * Obtain DrMOS current sense gain of power stage from the register + * , bits 0-2. The value is selected as below: + * 00b - 5µA/A, 01b - 8.5µA/A, 10b - 9.7µA/A, 11b - 10µA/A. Other + * values are reserved. + */ + ret = i2c_smbus_read_word_data(client, MP2888_MFR_SYS_CONFIG); + if (ret < 0) + return ret; + + switch (ret & MP2888_DRMOS_KCS) { + case 0: + data->curr_sense_gain = 85; + break; + case 1: + data->curr_sense_gain = 97; + break; + case 2: + data->curr_sense_gain = 100; + break; + case 3: + data->curr_sense_gain = 50; + break; + default: + return -EINVAL; + } + + /* + * Obtain resolution selector for total and phase current report and protection. + * 0: original resolution; 1: half resolution (in such case phase current value should + * be doubled. + */ + data->total_curr_resolution = (ret & MP2888_TOTAL_CURRENT_RESOLUTION) >> 3; + data->phase_curr_resolution = (ret & MP2888_PHASE_CURRENT_RESOLUTION) >> 4; + + return 0; +} + +static int +mp2888_read_phase(struct i2c_client *client, struct mp2888_data *data, int page, int phase, u8 reg) +{ + int ret; + + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + + if (!((phase + 1) % 2)) + ret >>= 8; + ret &= 0xff; + + /* + * Output value is calculated as: (READ_CSx / 80 – 1.23) / (Kcs * Rcs) + * where: + * - Kcs is the DrMOS current sense gain of power stage, which is obtained from the + * register MP2888_MFR_VR_CONFIG1, bits 13-12 with the following selection of DrMOS + * (data->curr_sense_gain): + * 00b - 8.5µA/A, 01b - 9.7µA/A, 1b - 10µA/A, 11b - 5µA/A. + * - Rcs is the internal phase current sense resistor. This parameter depends on hardware + * assembly. By default it is set to 1kΩ. In case of different assembly, user should + * scale this parameter by dividing it by Rcs. + * If phase current resolution bit is set to 1, READ_CSx value should be doubled. + * Note, that current phase sensing, providing by the device is not accurate. This is + * because sampling of current occurrence of bit weight has a big deviation, especially for + * light load. + */ + ret = DIV_ROUND_CLOSEST(ret * 200 - 19600, data->curr_sense_gain); + /* Scale according to total current resolution. */ + ret = (data->total_curr_resolution) ? ret * 2 : ret; + return ret; +} + +static int +mp2888_read_phases(struct i2c_client *client, struct mp2888_data *data, int page, int phase) +{ + int ret; + + switch (phase) { + case 0 ... 1: + ret = mp2888_read_phase(client, data, page, phase, MP2888_MFR_READ_CS1_2); + break; + case 2 ... 3: + ret = mp2888_read_phase(client, data, page, phase, MP2888_MFR_READ_CS3_4); + break; + case 4 ... 5: + ret = mp2888_read_phase(client, data, page, phase, MP2888_MFR_READ_CS5_6); + break; + case 6 ... 7: + ret = mp2888_read_phase(client, data, page, phase, MP2888_MFR_READ_CS7_8); + break; + case 8 ... 9: + ret = mp2888_read_phase(client, data, page, phase, MP2888_MFR_READ_CS9_10); + break; + default: + return -ENODATA; + } + return ret; +} + +static int mp2888_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 mp2888_data *data = to_mp2888_data(info); + int ret; + + switch (reg) { + case PMBUS_READ_VIN: + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret <= 0) + return ret; + + /* + * READ_VIN requires fixup to scale it to linear11 format. Register data format + * provides 10 bits for mantissa and 6 bits for exponent. Bits 15:10 are set with + * the fixed value 111011b. + */ + ret = (ret & GENMASK(9, 0)) | ((ret & GENMASK(31, 10)) << 1); + break; + case PMBUS_OT_WARN_LIMIT: + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + /* + * Chip reports limits in degrees C, but the actual temperature in 10th of + * degrees C - scaling is needed to match both. + */ + ret *= MP2888_TEMP_UNIT; + break; + case PMBUS_READ_IOUT: + if (phase != 0xff) + return mp2888_read_phases(client, data, page, phase); + + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + /* + * READ_IOUT register has unused bits 15:12 with fixed value 1110b. Clear these + * bits and scale with total current resolution. Data is provided in direct format. + */ + ret &= GENMASK(11, 0); + ret = data->total_curr_resolution ? ret * 2 : ret; + break; + case PMBUS_IOUT_OC_WARN_LIMIT: + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + ret &= GENMASK(9, 0); + /* + * Chip reports limits with resolution 1A or 2A, if total current resolution bit is + * set 1. Actual current is reported with 0.25A or respectively 0.5A resolution. + * Scaling is needed to match both. + */ + ret = data->total_curr_resolution ? ret * 8 : ret * 4; + break; + case PMBUS_READ_POUT: + case PMBUS_READ_PIN: + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + ret = data->total_curr_resolution ? ret : DIV_ROUND_CLOSEST(ret, 2); + break; + case PMBUS_POUT_OP_WARN_LIMIT: + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + /* + * Chip reports limits with resolution 1W or 2W, if total current resolution bit is + * set 1. Actual power is reported with 0.5W or 1W respectively resolution. Scaling + * is needed to match both. + */ + ret = data->total_curr_resolution ? ret * 2 : ret; + break; + /* + * The below registers are not implemented by device or implemented not according to the + * spec. Skip all of them to avoid exposing non-relevant inputs to sysfs. + */ + case PMBUS_OT_FAULT_LIMIT: + case PMBUS_UT_WARN_LIMIT: + case PMBUS_UT_FAULT_LIMIT: + case PMBUS_VIN_UV_FAULT_LIMIT: + case PMBUS_VOUT_UV_WARN_LIMIT: + case PMBUS_VOUT_OV_WARN_LIMIT: + case PMBUS_VOUT_UV_FAULT_LIMIT: + case PMBUS_VOUT_OV_FAULT_LIMIT: + case PMBUS_VIN_OV_WARN_LIMIT: + case PMBUS_IOUT_OC_LV_FAULT_LIMIT: + case PMBUS_IOUT_OC_FAULT_LIMIT: + case PMBUS_POUT_MAX: + case PMBUS_IOUT_UC_FAULT_LIMIT: + case PMBUS_POUT_OP_FAULT_LIMIT: + case PMBUS_PIN_OP_WARN_LIMIT: + case PMBUS_MFR_VIN_MIN: + case PMBUS_MFR_VOUT_MIN: + case PMBUS_MFR_VIN_MAX: + case PMBUS_MFR_VOUT_MAX: + case PMBUS_MFR_IIN_MAX: + case PMBUS_MFR_IOUT_MAX: + case PMBUS_MFR_PIN_MAX: + case PMBUS_MFR_POUT_MAX: + case PMBUS_MFR_MAX_TEMP_1: + return -ENXIO; + default: + return -ENODATA; + } + + return ret; +} + +static int mp2888_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 mp2888_data *data = to_mp2888_data(info); + + switch (reg) { + case PMBUS_OT_WARN_LIMIT: + word = DIV_ROUND_CLOSEST(word, MP2888_TEMP_UNIT); + /* Drop unused bits 15:8. */ + word = clamp_val(word, 0, GENMASK(7, 0)); + break; + case PMBUS_IOUT_OC_WARN_LIMIT: + /* Fix limit according to total curent resolution. */ + word = data->total_curr_resolution ? DIV_ROUND_CLOSEST(word, 8) : + DIV_ROUND_CLOSEST(word, 4); + /* Drop unused bits 15:10. */ + word = clamp_val(word, 0, GENMASK(9, 0)); + break; + case PMBUS_POUT_OP_WARN_LIMIT: + /* Fix limit according to total curent resolution. */ + word = data->total_curr_resolution ? DIV_ROUND_CLOSEST(word, 4) : + DIV_ROUND_CLOSEST(word, 2); + /* Drop unused bits 15:10. */ + word = clamp_val(word, 0, GENMASK(9, 0)); + break; + default: + return -ENODATA; + } + return pmbus_write_word_data(client, page, reg, word); +} + +static int +mp2888_identify_multiphase(struct i2c_client *client, struct mp2888_data *data, + struct pmbus_driver_info *info) +{ + int ret; + + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); + if (ret < 0) + return ret; + + /* Identify multiphase number - could be from 1 to 10. */ + ret = i2c_smbus_read_word_data(client, MP2888_MFR_VR_CONFIG1); + if (ret <= 0) + return ret; + + info->phases[0] = ret & GENMASK(3, 0); + + /* + * The device provides a total of 10 PWM pins, and can be configured to different phase + * count applications for rail. + */ + if (info->phases[0] > MP2888_MAX_PHASE) + return -EINVAL; + + return 0; +} + +static struct pmbus_driver_info mp2888_info = { + .pages = 1, + .format[PSC_VOLTAGE_IN] = linear, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .format[PSC_CURRENT_IN] = linear, + .format[PSC_CURRENT_OUT] = direct, + .format[PSC_POWER] = direct, + .m[PSC_TEMPERATURE] = 1, + .R[PSC_TEMPERATURE] = 1, + .m[PSC_VOLTAGE_OUT] = 1, + .R[PSC_VOLTAGE_OUT] = 3, + .m[PSC_CURRENT_OUT] = 4, + .m[PSC_POWER] = 1, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_IOUT | + PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT | + PMBUS_PHASE_VIRTUAL, + .pfunc[0] = PMBUS_HAVE_IOUT, + .pfunc[1] = PMBUS_HAVE_IOUT, + .pfunc[2] = PMBUS_HAVE_IOUT, + .pfunc[3] = PMBUS_HAVE_IOUT, + .pfunc[4] = PMBUS_HAVE_IOUT, + .pfunc[5] = PMBUS_HAVE_IOUT, + .pfunc[6] = PMBUS_HAVE_IOUT, + .pfunc[7] = PMBUS_HAVE_IOUT, + .pfunc[8] = PMBUS_HAVE_IOUT, + .pfunc[9] = PMBUS_HAVE_IOUT, + .read_byte_data = mp2888_read_byte_data, + .read_word_data = mp2888_read_word_data, + .write_word_data = mp2888_write_word_data, +}; + +static int mp2888_probe(struct i2c_client *client) +{ + struct pmbus_driver_info *info; + struct mp2888_data *data; + int ret; + + data = devm_kzalloc(&client->dev, sizeof(struct mp2888_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + memcpy(&data->info, &mp2888_info, sizeof(*info)); + info = &data->info; + + /* Identify multiphase configuration. */ + ret = mp2888_identify_multiphase(client, data, info); + if (ret) + return ret; + + /* Obtain current sense gain of power stage and current resolution. */ + ret = mp2888_current_sense_gain_and_resolution_get(client, data); + if (ret) + return ret; + + return pmbus_do_probe(client, info); +} + +static const struct i2c_device_id mp2888_id[] = { + {"mp2888", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, mp2888_id); + +static const struct of_device_id __maybe_unused mp2888_of_match[] = { + {.compatible = "mps,mp2888"}, + {} +}; +MODULE_DEVICE_TABLE(of, mp2888_of_match); + +static struct i2c_driver mp2888_driver = { + .driver = { + .name = "mp2888", + .of_match_table = of_match_ptr(mp2888_of_match), + }, + .probe_new = mp2888_probe, + .id_table = mp2888_id, +}; + +module_i2c_driver(mp2888_driver); + +MODULE_AUTHOR("Vadim Pasternak <vadimp@nvidia.com>"); +MODULE_DESCRIPTION("PMBus driver for MPS MP2888 device"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/mp2975.c b/drivers/hwmon/pmbus/mp2975.c new file mode 100644 index 000000000..51986adfb --- /dev/null +++ b/drivers/hwmon/pmbus/mp2975.c @@ -0,0 +1,769 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for MPS Multi-phase Digital VR Controllers + * + * Copyright (C) 2020 Nvidia Technologies Ltd. + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include "pmbus.h" + +/* Vendor specific registers. */ +#define MP2975_MFR_APS_HYS_R2 0x0d +#define MP2975_MFR_SLOPE_TRIM3 0x1d +#define MP2975_MFR_VR_MULTI_CONFIG_R1 0x0d +#define MP2975_MFR_VR_MULTI_CONFIG_R2 0x1d +#define MP2975_MFR_APS_DECAY_ADV 0x56 +#define MP2975_MFR_DC_LOOP_CTRL 0x59 +#define MP2975_MFR_OCP_UCP_PHASE_SET 0x65 +#define MP2975_MFR_VR_CONFIG1 0x68 +#define MP2975_MFR_READ_CS1_2 0x82 +#define MP2975_MFR_READ_CS3_4 0x83 +#define MP2975_MFR_READ_CS5_6 0x84 +#define MP2975_MFR_READ_CS7_8 0x85 +#define MP2975_MFR_READ_CS9_10 0x86 +#define MP2975_MFR_READ_CS11_12 0x87 +#define MP2975_MFR_READ_IOUT_PK 0x90 +#define MP2975_MFR_READ_POUT_PK 0x91 +#define MP2975_MFR_READ_VREF_R1 0xa1 +#define MP2975_MFR_READ_VREF_R2 0xa3 +#define MP2975_MFR_OVP_TH_SET 0xe5 +#define MP2975_MFR_UVP_SET 0xe6 + +#define MP2975_VOUT_FORMAT BIT(15) +#define MP2975_VID_STEP_SEL_R1 BIT(4) +#define MP2975_IMVP9_EN_R1 BIT(13) +#define MP2975_VID_STEP_SEL_R2 BIT(3) +#define MP2975_IMVP9_EN_R2 BIT(12) +#define MP2975_PRT_THRES_DIV_OV_EN BIT(14) +#define MP2975_DRMOS_KCS GENMASK(13, 12) +#define MP2975_PROT_DEV_OV_OFF 10 +#define MP2975_PROT_DEV_OV_ON 5 +#define MP2975_SENSE_AMPL BIT(11) +#define MP2975_SENSE_AMPL_UNIT 1 +#define MP2975_SENSE_AMPL_HALF 2 +#define MP2975_VIN_UV_LIMIT_UNIT 8 + +#define MP2975_MAX_PHASE_RAIL1 8 +#define MP2975_MAX_PHASE_RAIL2 4 +#define MP2975_PAGE_NUM 2 + +#define MP2975_RAIL2_FUNC (PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | \ + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | \ + PMBUS_HAVE_POUT | PMBUS_PHASE_VIRTUAL) + +struct mp2975_data { + struct pmbus_driver_info info; + int vout_scale; + int vid_step[MP2975_PAGE_NUM]; + int vref[MP2975_PAGE_NUM]; + int vref_off[MP2975_PAGE_NUM]; + int vout_max[MP2975_PAGE_NUM]; + int vout_ov_fixed[MP2975_PAGE_NUM]; + int vout_format[MP2975_PAGE_NUM]; + int curr_sense_gain[MP2975_PAGE_NUM]; +}; + +#define to_mp2975_data(x) container_of(x, struct mp2975_data, info) + +static int mp2975_read_byte_data(struct i2c_client *client, int page, int reg) +{ + switch (reg) { + case PMBUS_VOUT_MODE: + /* + * Enforce VOUT direct format, since device allows to set the + * different formats for the different rails. Conversion from + * VID to direct provided by driver internally, in case it is + * necessary. + */ + return PB_VOUT_MODE_DIRECT; + default: + return -ENODATA; + } +} + +static int +mp2975_read_word_helper(struct i2c_client *client, int page, int phase, u8 reg, + u16 mask) +{ + int ret = pmbus_read_word_data(client, page, phase, reg); + + return (ret > 0) ? ret & mask : ret; +} + +static int +mp2975_vid2direct(int vrf, int val) +{ + switch (vrf) { + case vr12: + if (val >= 0x01) + return 250 + (val - 1) * 5; + break; + case vr13: + if (val >= 0x01) + return 500 + (val - 1) * 10; + break; + case imvp9: + if (val >= 0x01) + return 200 + (val - 1) * 10; + break; + default: + return -EINVAL; + } + return 0; +} + +static int +mp2975_read_phase(struct i2c_client *client, struct mp2975_data *data, + int page, int phase, u8 reg) +{ + int ph_curr, ret; + + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + + if (!((phase + 1) % MP2975_PAGE_NUM)) + ret >>= 8; + ret &= 0xff; + + /* + * Output value is calculated as: (READ_CSx / 80 – 1.23) / (Kcs * Rcs) + * where: + * - Kcs is the DrMOS current sense gain of power stage, which is + * obtained from the register MP2975_MFR_VR_CONFIG1, bits 13-12 with + * the following selection of DrMOS (data->curr_sense_gain[page]): + * 00b - 5µA/A, 01b - 8.5µA/A, 10b - 9.7µA/A, 11b - 10µA/A. + * - Rcs is the internal phase current sense resistor which is constant + * value 1kΩ. + */ + ph_curr = ret * 100 - 9800; + + /* + * Current phase sensing, providing by the device is not accurate + * for the light load. This because sampling of current occurrence of + * bit weight has a big deviation for light load. For handling such + * case phase current is represented as the maximum between the value + * calculated above and total rail current divided by number phases. + */ + ret = pmbus_read_word_data(client, page, phase, PMBUS_READ_IOUT); + if (ret < 0) + return ret; + + return max_t(int, DIV_ROUND_CLOSEST(ret, data->info.phases[page]), + DIV_ROUND_CLOSEST(ph_curr, data->curr_sense_gain[page])); +} + +static int +mp2975_read_phases(struct i2c_client *client, struct mp2975_data *data, + int page, int phase) +{ + int ret; + + if (page) { + switch (phase) { + case 0 ... 1: + ret = mp2975_read_phase(client, data, page, phase, + MP2975_MFR_READ_CS7_8); + break; + case 2 ... 3: + ret = mp2975_read_phase(client, data, page, phase, + MP2975_MFR_READ_CS9_10); + break; + case 4 ... 5: + ret = mp2975_read_phase(client, data, page, phase, + MP2975_MFR_READ_CS11_12); + break; + default: + return -ENODATA; + } + } else { + switch (phase) { + case 0 ... 1: + ret = mp2975_read_phase(client, data, page, phase, + MP2975_MFR_READ_CS1_2); + break; + case 2 ... 3: + ret = mp2975_read_phase(client, data, page, phase, + MP2975_MFR_READ_CS3_4); + break; + case 4 ... 5: + ret = mp2975_read_phase(client, data, page, phase, + MP2975_MFR_READ_CS5_6); + break; + case 6 ... 7: + ret = mp2975_read_phase(client, data, page, phase, + MP2975_MFR_READ_CS7_8); + break; + case 8 ... 9: + ret = mp2975_read_phase(client, data, page, phase, + MP2975_MFR_READ_CS9_10); + break; + case 10 ... 11: + ret = mp2975_read_phase(client, data, page, phase, + MP2975_MFR_READ_CS11_12); + break; + default: + return -ENODATA; + } + } + return ret; +} + +static int mp2975_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 mp2975_data *data = to_mp2975_data(info); + int ret; + + switch (reg) { + case PMBUS_OT_FAULT_LIMIT: + ret = mp2975_read_word_helper(client, page, phase, reg, + GENMASK(7, 0)); + break; + case PMBUS_VIN_OV_FAULT_LIMIT: + ret = mp2975_read_word_helper(client, page, phase, reg, + GENMASK(7, 0)); + if (ret < 0) + return ret; + + ret = DIV_ROUND_CLOSEST(ret, MP2975_VIN_UV_LIMIT_UNIT); + break; + case PMBUS_VOUT_OV_FAULT_LIMIT: + /* + * Register provides two values for over-voltage protection + * threshold for fixed (ovp2) and tracking (ovp1) modes. The + * minimum of these two values is provided as over-voltage + * fault alarm. + */ + ret = mp2975_read_word_helper(client, page, phase, + MP2975_MFR_OVP_TH_SET, + GENMASK(2, 0)); + if (ret < 0) + return ret; + + ret = min_t(int, data->vout_max[page] + 50 * (ret + 1), + data->vout_ov_fixed[page]); + break; + case PMBUS_VOUT_UV_FAULT_LIMIT: + ret = mp2975_read_word_helper(client, page, phase, + MP2975_MFR_UVP_SET, + GENMASK(2, 0)); + if (ret < 0) + return ret; + + ret = DIV_ROUND_CLOSEST(data->vref[page] * 10 - 50 * + (ret + 1) * data->vout_scale, 10); + break; + case PMBUS_READ_VOUT: + ret = mp2975_read_word_helper(client, page, phase, reg, + GENMASK(11, 0)); + if (ret < 0) + return ret; + + /* + * READ_VOUT can be provided in VID or direct format. The + * format type is specified by bit 15 of the register + * MP2975_MFR_DC_LOOP_CTRL. The driver enforces VOUT direct + * format, since device allows to set the different formats for + * the different rails and also all VOUT limits registers are + * provided in a direct format. In case format is VID - convert + * to direct. + */ + if (data->vout_format[page] == vid) + ret = mp2975_vid2direct(info->vrm_version[page], ret); + break; + case PMBUS_VIRT_READ_POUT_MAX: + ret = mp2975_read_word_helper(client, page, phase, + MP2975_MFR_READ_POUT_PK, + GENMASK(12, 0)); + if (ret < 0) + return ret; + + ret = DIV_ROUND_CLOSEST(ret, 4); + break; + case PMBUS_VIRT_READ_IOUT_MAX: + ret = mp2975_read_word_helper(client, page, phase, + MP2975_MFR_READ_IOUT_PK, + GENMASK(12, 0)); + if (ret < 0) + return ret; + + ret = DIV_ROUND_CLOSEST(ret, 4); + break; + case PMBUS_READ_IOUT: + ret = mp2975_read_phases(client, data, page, phase); + if (ret < 0) + return ret; + + break; + case PMBUS_UT_WARN_LIMIT: + case PMBUS_UT_FAULT_LIMIT: + case PMBUS_VIN_UV_WARN_LIMIT: + case PMBUS_VIN_UV_FAULT_LIMIT: + case PMBUS_VOUT_UV_WARN_LIMIT: + case PMBUS_VOUT_OV_WARN_LIMIT: + case PMBUS_VIN_OV_WARN_LIMIT: + case PMBUS_IIN_OC_FAULT_LIMIT: + case PMBUS_IOUT_OC_LV_FAULT_LIMIT: + case PMBUS_IIN_OC_WARN_LIMIT: + case PMBUS_IOUT_OC_WARN_LIMIT: + case PMBUS_IOUT_OC_FAULT_LIMIT: + case PMBUS_IOUT_UC_FAULT_LIMIT: + case PMBUS_POUT_OP_FAULT_LIMIT: + case PMBUS_POUT_OP_WARN_LIMIT: + case PMBUS_PIN_OP_WARN_LIMIT: + return -ENXIO; + default: + return -ENODATA; + } + + return ret; +} + +static int mp2975_identify_multiphase_rail2(struct i2c_client *client) +{ + int ret; + + /* + * Identify multiphase for rail 2 - could be from 0 to 4. + * In case phase number is zero – only page zero is supported + */ + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 2); + if (ret < 0) + return ret; + + /* Identify multiphase for rail 2 - could be from 0 to 4. */ + ret = i2c_smbus_read_word_data(client, MP2975_MFR_VR_MULTI_CONFIG_R2); + if (ret < 0) + return ret; + + ret &= GENMASK(2, 0); + return (ret >= 4) ? 4 : ret; +} + +static void mp2975_set_phase_rail1(struct pmbus_driver_info *info) +{ + int i; + + for (i = 0 ; i < info->phases[0]; i++) + info->pfunc[i] = PMBUS_HAVE_IOUT; +} + +static void +mp2975_set_phase_rail2(struct pmbus_driver_info *info, int num_phases) +{ + int i; + + /* Set phases for rail 2 from upper to lower. */ + for (i = 1; i <= num_phases; i++) + info->pfunc[MP2975_MAX_PHASE_RAIL1 - i] = PMBUS_HAVE_IOUT; +} + +static int +mp2975_identify_multiphase(struct i2c_client *client, struct mp2975_data *data, + struct pmbus_driver_info *info) +{ + int num_phases2, ret; + + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 2); + if (ret < 0) + return ret; + + /* Identify multiphase for rail 1 - could be from 1 to 8. */ + ret = i2c_smbus_read_word_data(client, MP2975_MFR_VR_MULTI_CONFIG_R1); + if (ret <= 0) + return ret; + + info->phases[0] = ret & GENMASK(3, 0); + + /* + * The device provides a total of 8 PWM pins, and can be configured + * to different phase count applications for rail 1 and rail 2. + * Rail 1 can be set to 8 phases, while rail 2 can only be set to 4 + * phases at most. When rail 1’s phase count is configured as 0, rail + * 1 operates with 1-phase DCM. When rail 2 phase count is configured + * as 0, rail 2 is disabled. + */ + if (info->phases[0] > MP2975_MAX_PHASE_RAIL1) + return -EINVAL; + + mp2975_set_phase_rail1(info); + num_phases2 = min(MP2975_MAX_PHASE_RAIL1 - info->phases[0], + MP2975_MAX_PHASE_RAIL2); + if (info->phases[1] && info->phases[1] <= num_phases2) + mp2975_set_phase_rail2(info, num_phases2); + + return 0; +} + +static int +mp2975_identify_vid(struct i2c_client *client, struct mp2975_data *data, + struct pmbus_driver_info *info, u32 reg, int page, + u32 imvp_bit, u32 vr_bit) +{ + int ret; + + /* Identify VID mode and step selection. */ + ret = i2c_smbus_read_word_data(client, reg); + if (ret < 0) + return ret; + + if (ret & imvp_bit) { + info->vrm_version[page] = imvp9; + data->vid_step[page] = MP2975_PROT_DEV_OV_OFF; + } else if (ret & vr_bit) { + info->vrm_version[page] = vr12; + data->vid_step[page] = MP2975_PROT_DEV_OV_ON; + } else { + info->vrm_version[page] = vr13; + data->vid_step[page] = MP2975_PROT_DEV_OV_OFF; + } + + return 0; +} + +static int +mp2975_identify_rails_vid(struct i2c_client *client, struct mp2975_data *data, + struct pmbus_driver_info *info) +{ + int ret; + + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 2); + if (ret < 0) + return ret; + + /* Identify VID mode for rail 1. */ + ret = mp2975_identify_vid(client, data, info, + MP2975_MFR_VR_MULTI_CONFIG_R1, 0, + MP2975_IMVP9_EN_R1, MP2975_VID_STEP_SEL_R1); + if (ret < 0) + return ret; + + /* Identify VID mode for rail 2, if connected. */ + if (info->phases[1]) + ret = mp2975_identify_vid(client, data, info, + MP2975_MFR_VR_MULTI_CONFIG_R2, 1, + MP2975_IMVP9_EN_R2, + MP2975_VID_STEP_SEL_R2); + return ret; +} + +static int +mp2975_current_sense_gain_get(struct i2c_client *client, + struct mp2975_data *data) +{ + int i, ret; + + /* + * Obtain DrMOS current sense gain of power stage from the register + * MP2975_MFR_VR_CONFIG1, bits 13-12. The value is selected as below: + * 00b - 5µA/A, 01b - 8.5µA/A, 10b - 9.7µA/A, 11b - 10µA/A. Other + * values are invalid. + */ + for (i = 0 ; i < data->info.pages; i++) { + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, i); + if (ret < 0) + return ret; + ret = i2c_smbus_read_word_data(client, + MP2975_MFR_VR_CONFIG1); + if (ret < 0) + return ret; + + switch ((ret & MP2975_DRMOS_KCS) >> 12) { + case 0: + data->curr_sense_gain[i] = 50; + break; + case 1: + data->curr_sense_gain[i] = 85; + break; + case 2: + data->curr_sense_gain[i] = 97; + break; + default: + data->curr_sense_gain[i] = 100; + break; + } + } + + return 0; +} + +static int +mp2975_vref_get(struct i2c_client *client, struct mp2975_data *data, + struct pmbus_driver_info *info) +{ + int ret; + + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 3); + if (ret < 0) + return ret; + + /* Get voltage reference value for rail 1. */ + ret = i2c_smbus_read_word_data(client, MP2975_MFR_READ_VREF_R1); + if (ret < 0) + return ret; + + data->vref[0] = ret * data->vid_step[0]; + + /* Get voltage reference value for rail 2, if connected. */ + if (data->info.pages == MP2975_PAGE_NUM) { + ret = i2c_smbus_read_word_data(client, MP2975_MFR_READ_VREF_R2); + if (ret < 0) + return ret; + + data->vref[1] = ret * data->vid_step[1]; + } + return 0; +} + +static int +mp2975_vref_offset_get(struct i2c_client *client, struct mp2975_data *data, + int page) +{ + int ret; + + ret = i2c_smbus_read_word_data(client, MP2975_MFR_OVP_TH_SET); + if (ret < 0) + return ret; + + switch ((ret & GENMASK(5, 3)) >> 3) { + case 1: + data->vref_off[page] = 140; + break; + case 2: + data->vref_off[page] = 220; + break; + case 4: + data->vref_off[page] = 400; + break; + default: + return -EINVAL; + } + return 0; +} + +static int +mp2975_vout_max_get(struct i2c_client *client, struct mp2975_data *data, + struct pmbus_driver_info *info, int page) +{ + int ret; + + /* Get maximum reference voltage of VID-DAC in VID format. */ + ret = i2c_smbus_read_word_data(client, PMBUS_VOUT_MAX); + if (ret < 0) + return ret; + + data->vout_max[page] = mp2975_vid2direct(info->vrm_version[page], ret & + GENMASK(8, 0)); + return 0; +} + +static int +mp2975_identify_vout_format(struct i2c_client *client, + struct mp2975_data *data, int page) +{ + int ret; + + ret = i2c_smbus_read_word_data(client, MP2975_MFR_DC_LOOP_CTRL); + if (ret < 0) + return ret; + + if (ret & MP2975_VOUT_FORMAT) + data->vout_format[page] = vid; + else + data->vout_format[page] = direct; + return 0; +} + +static int +mp2975_vout_ov_scale_get(struct i2c_client *client, struct mp2975_data *data, + struct pmbus_driver_info *info) +{ + int thres_dev, sense_ampl, ret; + + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); + if (ret < 0) + return ret; + + /* + * Get divider for over- and under-voltage protection thresholds + * configuration from the Advanced Options of Auto Phase Shedding and + * decay register. + */ + ret = i2c_smbus_read_word_data(client, MP2975_MFR_APS_DECAY_ADV); + if (ret < 0) + return ret; + thres_dev = ret & MP2975_PRT_THRES_DIV_OV_EN ? MP2975_PROT_DEV_OV_ON : + MP2975_PROT_DEV_OV_OFF; + + /* Select the gain of remote sense amplifier. */ + ret = i2c_smbus_read_word_data(client, PMBUS_VOUT_SCALE_LOOP); + if (ret < 0) + return ret; + sense_ampl = ret & MP2975_SENSE_AMPL ? MP2975_SENSE_AMPL_HALF : + MP2975_SENSE_AMPL_UNIT; + + data->vout_scale = sense_ampl * thres_dev; + + return 0; +} + +static int +mp2975_vout_per_rail_config_get(struct i2c_client *client, + struct mp2975_data *data, + struct pmbus_driver_info *info) +{ + int i, ret; + + for (i = 0; i < data->info.pages; i++) { + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, i); + if (ret < 0) + return ret; + + /* Obtain voltage reference offsets. */ + ret = mp2975_vref_offset_get(client, data, i); + if (ret < 0) + return ret; + + /* Obtain maximum voltage values. */ + ret = mp2975_vout_max_get(client, data, info, i); + if (ret < 0) + return ret; + + /* + * Get VOUT format for READ_VOUT command : VID or direct. + * Pages on same device can be configured with different + * formats. + */ + ret = mp2975_identify_vout_format(client, data, i); + if (ret < 0) + return ret; + + /* + * Set over-voltage fixed value. Thresholds are provided as + * fixed value, and tracking value. The minimum of them are + * exposed as over-voltage critical threshold. + */ + data->vout_ov_fixed[i] = data->vref[i] + + DIV_ROUND_CLOSEST(data->vref_off[i] * + data->vout_scale, + 10); + } + + return 0; +} + +static struct pmbus_driver_info mp2975_info = { + .pages = 1, + .format[PSC_VOLTAGE_IN] = linear, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .format[PSC_CURRENT_IN] = linear, + .format[PSC_CURRENT_OUT] = direct, + .format[PSC_POWER] = direct, + .m[PSC_TEMPERATURE] = 1, + .m[PSC_VOLTAGE_OUT] = 1, + .R[PSC_VOLTAGE_OUT] = 3, + .m[PSC_CURRENT_OUT] = 1, + .m[PSC_POWER] = 1, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | PMBUS_HAVE_POUT | + PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT | PMBUS_PHASE_VIRTUAL, + .read_byte_data = mp2975_read_byte_data, + .read_word_data = mp2975_read_word_data, +}; + +static int mp2975_probe(struct i2c_client *client) +{ + struct pmbus_driver_info *info; + struct mp2975_data *data; + int ret; + + data = devm_kzalloc(&client->dev, sizeof(struct mp2975_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + memcpy(&data->info, &mp2975_info, sizeof(*info)); + info = &data->info; + + /* Identify multiphase configuration for rail 2. */ + ret = mp2975_identify_multiphase_rail2(client); + if (ret < 0) + return ret; + + if (ret) { + /* Two rails are connected. */ + data->info.pages = MP2975_PAGE_NUM; + data->info.phases[1] = ret; + data->info.func[1] = MP2975_RAIL2_FUNC; + } + + /* Identify multiphase configuration. */ + ret = mp2975_identify_multiphase(client, data, info); + if (ret) + return ret; + + /* Identify VID setting per rail. */ + ret = mp2975_identify_rails_vid(client, data, info); + if (ret < 0) + return ret; + + /* Obtain current sense gain of power stage. */ + ret = mp2975_current_sense_gain_get(client, data); + if (ret) + return ret; + + /* Obtain voltage reference values. */ + ret = mp2975_vref_get(client, data, info); + if (ret) + return ret; + + /* Obtain vout over-voltage scales. */ + ret = mp2975_vout_ov_scale_get(client, data, info); + if (ret < 0) + return ret; + + /* Obtain offsets, maximum and format for vout. */ + ret = mp2975_vout_per_rail_config_get(client, data, info); + if (ret) + return ret; + + return pmbus_do_probe(client, info); +} + +static const struct i2c_device_id mp2975_id[] = { + {"mp2975", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, mp2975_id); + +static const struct of_device_id __maybe_unused mp2975_of_match[] = { + {.compatible = "mps,mp2975"}, + {} +}; +MODULE_DEVICE_TABLE(of, mp2975_of_match); + +static struct i2c_driver mp2975_driver = { + .driver = { + .name = "mp2975", + .of_match_table = of_match_ptr(mp2975_of_match), + }, + .probe_new = mp2975_probe, + .id_table = mp2975_id, +}; + +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/mp5023.c b/drivers/hwmon/pmbus/mp5023.c new file mode 100644 index 000000000..791a06c3c --- /dev/null +++ b/drivers/hwmon/pmbus/mp5023.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for MPS MP5023 Hot-Swap Controller + */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include "pmbus.h" + +static struct pmbus_driver_info mp5023_info = { + .pages = 1, + + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_CURRENT_OUT] = direct, + .format[PSC_POWER] = direct, + .format[PSC_TEMPERATURE] = direct, + + .m[PSC_VOLTAGE_IN] = 32, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 0, + .m[PSC_VOLTAGE_OUT] = 32, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 0, + .m[PSC_CURRENT_OUT] = 16, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = 0, + .m[PSC_POWER] = 1, + .b[PSC_POWER] = 0, + .R[PSC_POWER] = 0, + .m[PSC_TEMPERATURE] = 2, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 0, + + .func[0] = + PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_PIN | + PMBUS_HAVE_TEMP | PMBUS_HAVE_IOUT | + PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP, +}; + +static int mp5023_probe(struct i2c_client *client) +{ + return pmbus_do_probe(client, &mp5023_info); +} + +static const struct of_device_id __maybe_unused mp5023_of_match[] = { + { .compatible = "mps,mp5023", }, + {} +}; + +MODULE_DEVICE_TABLE(of, mp5023_of_match); + +static struct i2c_driver mp5023_driver = { + .driver = { + .name = "mp5023", + .of_match_table = of_match_ptr(mp5023_of_match), + }, + .probe_new = mp5023_probe, +}; + +module_i2c_driver(mp5023_driver); + +MODULE_AUTHOR("Howard Chiu <howard.chiu@quantatw.com>"); +MODULE_DESCRIPTION("PMBus driver for MPS MP5023 HSC"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/pim4328.c b/drivers/hwmon/pmbus/pim4328.c new file mode 100644 index 000000000..273ff6e57 --- /dev/null +++ b/drivers/hwmon/pmbus/pim4328.c @@ -0,0 +1,233 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for PIM4006, PIM4328 and PIM4820 + * + * Copyright (c) 2021 Flextronics International Sweden AB + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pmbus.h> +#include <linux/slab.h> +#include "pmbus.h" + +enum chips { pim4006, pim4328, pim4820 }; + +struct pim4328_data { + enum chips id; + struct pmbus_driver_info info; +}; + +#define to_pim4328_data(x) container_of(x, struct pim4328_data, info) + +/* PIM4006 and PIM4328 */ +#define PIM4328_MFR_READ_VINA 0xd3 +#define PIM4328_MFR_READ_VINB 0xd4 + +/* PIM4006 */ +#define PIM4328_MFR_READ_IINA 0xd6 +#define PIM4328_MFR_READ_IINB 0xd7 +#define PIM4328_MFR_FET_CHECKSTATUS 0xd9 + +/* PIM4328 */ +#define PIM4328_MFR_STATUS_BITS 0xd5 + +/* PIM4820 */ +#define PIM4328_MFR_READ_STATUS 0xd0 + +static const struct i2c_device_id pim4328_id[] = { + {"bmr455", pim4328}, + {"pim4006", pim4006}, + {"pim4106", pim4006}, + {"pim4206", pim4006}, + {"pim4306", pim4006}, + {"pim4328", pim4328}, + {"pim4406", pim4006}, + {"pim4820", pim4820}, + {} +}; +MODULE_DEVICE_TABLE(i2c, pim4328_id); + +static int pim4328_read_word_data(struct i2c_client *client, int page, + int phase, int reg) +{ + int ret; + + if (page > 0) + return -ENXIO; + + if (phase == 0xff) + return -ENODATA; + + switch (reg) { + case PMBUS_READ_VIN: + ret = pmbus_read_word_data(client, page, phase, + phase == 0 ? PIM4328_MFR_READ_VINA + : PIM4328_MFR_READ_VINB); + break; + case PMBUS_READ_IIN: + ret = pmbus_read_word_data(client, page, phase, + phase == 0 ? PIM4328_MFR_READ_IINA + : PIM4328_MFR_READ_IINB); + break; + default: + ret = -ENODATA; + } + + return ret; +} + +static int pim4328_read_byte_data(struct i2c_client *client, int page, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct pim4328_data *data = to_pim4328_data(info); + int ret, status; + + if (page > 0) + return -ENXIO; + + switch (reg) { + case PMBUS_STATUS_BYTE: + ret = pmbus_read_byte_data(client, page, PMBUS_STATUS_BYTE); + if (ret < 0) + return ret; + if (data->id == pim4006) { + status = pmbus_read_word_data(client, page, 0xff, + PIM4328_MFR_FET_CHECKSTATUS); + if (status < 0) + return status; + if (status & 0x0630) /* Input UV */ + ret |= PB_STATUS_VIN_UV; + } else if (data->id == pim4328) { + status = pmbus_read_byte_data(client, page, + PIM4328_MFR_STATUS_BITS); + if (status < 0) + return status; + if (status & 0x04) /* Input UV */ + ret |= PB_STATUS_VIN_UV; + if (status & 0x40) /* Output UV */ + ret |= PB_STATUS_NONE_ABOVE; + } else if (data->id == pim4820) { + status = pmbus_read_byte_data(client, page, + PIM4328_MFR_READ_STATUS); + if (status < 0) + return status; + if (status & 0x05) /* Input OV or OC */ + ret |= PB_STATUS_NONE_ABOVE; + if (status & 0x1a) /* Input UV */ + ret |= PB_STATUS_VIN_UV; + if (status & 0x40) /* OT */ + ret |= PB_STATUS_TEMPERATURE; + } + break; + default: + ret = -ENODATA; + } + + return ret; +} + +static int pim4328_probe(struct i2c_client *client) +{ + int status; + u8 device_id[I2C_SMBUS_BLOCK_MAX + 1]; + const struct i2c_device_id *mid; + struct pim4328_data *data; + struct pmbus_driver_info *info; + struct pmbus_platform_data *pdata; + struct device *dev = &client->dev; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_BYTE_DATA + | I2C_FUNC_SMBUS_BLOCK_DATA)) + return -ENODEV; + + data = devm_kzalloc(&client->dev, sizeof(struct pim4328_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + 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 = pim4328_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; + } + + if (strcmp(client->name, mid->name)) + dev_notice(&client->dev, + "Device mismatch: Configured %s, detected %s\n", + client->name, mid->name); + + data->id = mid->driver_data; + info = &data->info; + info->pages = 1; + info->read_byte_data = pim4328_read_byte_data; + info->read_word_data = pim4328_read_word_data; + + pdata = devm_kzalloc(dev, sizeof(struct pmbus_platform_data), + GFP_KERNEL); + if (!pdata) + return -ENOMEM; + dev->platform_data = pdata; + pdata->flags = PMBUS_NO_CAPABILITY | PMBUS_NO_WRITE_PROTECT; + + switch (data->id) { + case pim4006: + info->phases[0] = 2; + info->func[0] = PMBUS_PHASE_VIRTUAL | PMBUS_HAVE_VIN + | PMBUS_HAVE_TEMP | PMBUS_HAVE_IOUT; + info->pfunc[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN; + info->pfunc[1] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN; + break; + case pim4328: + info->phases[0] = 2; + info->func[0] = PMBUS_PHASE_VIRTUAL + | PMBUS_HAVE_VCAP | PMBUS_HAVE_VIN + | PMBUS_HAVE_TEMP | PMBUS_HAVE_IOUT; + info->pfunc[0] = PMBUS_HAVE_VIN; + info->pfunc[1] = PMBUS_HAVE_VIN; + info->format[PSC_VOLTAGE_IN] = direct; + info->format[PSC_TEMPERATURE] = direct; + info->format[PSC_CURRENT_OUT] = direct; + pdata->flags |= PMBUS_USE_COEFFICIENTS_CMD; + break; + case pim4820: + info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_TEMP + | PMBUS_HAVE_IIN; + info->format[PSC_VOLTAGE_IN] = direct; + info->format[PSC_TEMPERATURE] = direct; + info->format[PSC_CURRENT_IN] = direct; + pdata->flags |= PMBUS_USE_COEFFICIENTS_CMD; + break; + default: + return -ENODEV; + } + + return pmbus_do_probe(client, info); +} + +static struct i2c_driver pim4328_driver = { + .driver = { + .name = "pim4328", + }, + .probe_new = pim4328_probe, + .id_table = pim4328_id, +}; + +module_i2c_driver(pim4328_driver); + +MODULE_AUTHOR("Erik Rosen <erik.rosen@metormote.com>"); +MODULE_DESCRIPTION("PMBus driver for PIM4006, PIM4328, PIM4820 power interface modules"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/pli1209bc.c b/drivers/hwmon/pmbus/pli1209bc.c new file mode 100644 index 000000000..05b4ee35b --- /dev/null +++ b/drivers/hwmon/pmbus/pli1209bc.c @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Hardware monitoring driver for Vicor PLI1209BC Digital Supervisor + * + * Copyright (c) 2022 9elements GmbH + */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pmbus.h> +#include <linux/regulator/driver.h> +#include "pmbus.h" + +/* + * The capability command is only supported at page 0. Probing the device while + * the page register is set to 1 will falsely enable PEC support. Disable + * capability probing accordingly, since the PLI1209BC does not have any + * additional capabilities. + */ +static struct pmbus_platform_data pli1209bc_plat_data = { + .flags = PMBUS_NO_CAPABILITY, +}; + +static int pli1209bc_read_word_data(struct i2c_client *client, int page, + int phase, int reg) +{ + int data; + + switch (reg) { + /* PMBUS_READ_POUT uses a direct format with R=0 */ + case PMBUS_READ_POUT: + data = pmbus_read_word_data(client, page, phase, reg); + if (data < 0) + return data; + data = sign_extend32(data, 15) * 10; + return clamp_val(data, -32768, 32767) & 0xffff; + /* + * PMBUS_READ_VOUT and PMBUS_READ_TEMPERATURE_1 return invalid data + * when the BCM is turned off. Since it is not possible to return + * ENODATA error, return zero instead. + */ + case PMBUS_READ_VOUT: + case PMBUS_READ_TEMPERATURE_1: + data = pmbus_read_word_data(client, page, phase, + PMBUS_STATUS_WORD); + if (data < 0) + return data; + if (data & PB_STATUS_POWER_GOOD_N) + return 0; + return pmbus_read_word_data(client, page, phase, reg); + default: + return -ENODATA; + } +} + +#if IS_ENABLED(CONFIG_SENSORS_PLI1209BC_REGULATOR) +static const struct regulator_desc pli1209bc_reg_desc = { + .name = "vout2", + .id = 1, + .of_match = of_match_ptr("vout2"), + .regulators_node = of_match_ptr("regulators"), + .ops = &pmbus_regulator_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, +}; +#endif + +static struct pmbus_driver_info pli1209bc_info = { + .pages = 2, + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_CURRENT_IN] = direct, + .format[PSC_CURRENT_OUT] = direct, + .format[PSC_POWER] = direct, + .format[PSC_TEMPERATURE] = direct, + .m[PSC_VOLTAGE_IN] = 1, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 1, + .m[PSC_VOLTAGE_OUT] = 1, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 1, + .m[PSC_CURRENT_IN] = 1, + .b[PSC_CURRENT_IN] = 0, + .R[PSC_CURRENT_IN] = 3, + .m[PSC_CURRENT_OUT] = 1, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = 2, + .m[PSC_POWER] = 1, + .b[PSC_POWER] = 0, + .R[PSC_POWER] = 1, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 0, + /* + * Page 0 sums up all attributes except voltage readings. + * The pli1209 digital supervisor only contains a single BCM, making + * page 0 redundant. + */ + .func[1] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT + | PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT + | PMBUS_HAVE_PIN | PMBUS_HAVE_POUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP + | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_INPUT, + .read_word_data = pli1209bc_read_word_data, +#if IS_ENABLED(CONFIG_SENSORS_PLI1209BC_REGULATOR) + .num_regulators = 1, + .reg_desc = &pli1209bc_reg_desc, +#endif +}; + +static int pli1209bc_probe(struct i2c_client *client) +{ + client->dev.platform_data = &pli1209bc_plat_data; + return pmbus_do_probe(client, &pli1209bc_info); +} + +static const struct i2c_device_id pli1209bc_id[] = { + {"pli1209bc", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, pli1209bc_id); + +#ifdef CONFIG_OF +static const struct of_device_id pli1209bc_of_match[] = { + { .compatible = "vicor,pli1209bc" }, + { }, +}; +MODULE_DEVICE_TABLE(of, pli1209bc_of_match); +#endif + +static struct i2c_driver pli1209bc_driver = { + .driver = { + .name = "pli1209bc", + .of_match_table = of_match_ptr(pli1209bc_of_match), + }, + .probe_new = pli1209bc_probe, + .id_table = pli1209bc_id, +}; + +module_i2c_driver(pli1209bc_driver); + +MODULE_AUTHOR("Marcello Sylvester Bauer <sylv@sylv.io>"); +MODULE_DESCRIPTION("PMBus driver for Vicor PLI1209BC"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/pm6764tr.c b/drivers/hwmon/pmbus/pm6764tr.c new file mode 100644 index 000000000..e0bbc8a30 --- /dev/null +++ b/drivers/hwmon/pmbus/pm6764tr.c @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Hardware monitoring driver for STMicroelectronics digital controller PM6764TR + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pmbus.h> +#include "pmbus.h" + +#define PM6764TR_PMBUS_READ_VOUT 0xD4 + +static int pm6764tr_read_word_data(struct i2c_client *client, int page, int phase, int reg) +{ + int ret; + + switch (reg) { + case PMBUS_VIRT_READ_VMON: + ret = pmbus_read_word_data(client, page, phase, PM6764TR_PMBUS_READ_VOUT); + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static struct pmbus_driver_info pm6764tr_info = { + .pages = 1, + .format[PSC_VOLTAGE_IN] = linear, + .format[PSC_VOLTAGE_OUT] = vid, + .format[PSC_TEMPERATURE] = linear, + .format[PSC_CURRENT_OUT] = linear, + .format[PSC_POWER] = linear, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN | PMBUS_HAVE_PIN | + PMBUS_HAVE_IOUT | PMBUS_HAVE_POUT | PMBUS_HAVE_VMON | + PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .read_word_data = pm6764tr_read_word_data, +}; + +static int pm6764tr_probe(struct i2c_client *client) +{ + return pmbus_do_probe(client, &pm6764tr_info); +} + +static const struct i2c_device_id pm6764tr_id[] = { + {"pm6764tr", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, pm6764tr_id); + +static const struct of_device_id __maybe_unused pm6764tr_of_match[] = { + {.compatible = "st,pm6764tr"}, + {} +}; + +/* This is the driver that will be inserted */ +static struct i2c_driver pm6764tr_driver = { + .driver = { + .name = "pm6764tr", + .of_match_table = of_match_ptr(pm6764tr_of_match), + }, + .probe_new = pm6764tr_probe, + .id_table = pm6764tr_id, +}; + +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 new file mode 100644 index 000000000..d0d386990 --- /dev/null +++ b/drivers/hwmon/pmbus/pmbus.c @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for PMBus devices + * + * Copyright (c) 2010, 2011 Ericsson AB. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/i2c.h> +#include <linux/pmbus.h> +#include "pmbus.h" + +struct pmbus_device_info { + int pages; + u32 flags; +}; + +static const struct i2c_device_id pmbus_id[]; + +/* + * Find sensor groups and status registers on each page. + */ +static void pmbus_find_sensor_groups(struct i2c_client *client, + struct pmbus_driver_info *info) +{ + int page; + + /* Sensors detected on page 0 only */ + if (pmbus_check_word_register(client, 0, PMBUS_READ_VIN)) + info->func[0] |= PMBUS_HAVE_VIN; + if (pmbus_check_word_register(client, 0, PMBUS_READ_VCAP)) + info->func[0] |= PMBUS_HAVE_VCAP; + if (pmbus_check_word_register(client, 0, PMBUS_READ_IIN)) + info->func[0] |= PMBUS_HAVE_IIN; + if (pmbus_check_word_register(client, 0, PMBUS_READ_PIN)) + info->func[0] |= PMBUS_HAVE_PIN; + if (info->func[0] + && pmbus_check_byte_register(client, 0, PMBUS_STATUS_INPUT)) + info->func[0] |= PMBUS_HAVE_STATUS_INPUT; + if (pmbus_check_byte_register(client, 0, PMBUS_FAN_CONFIG_12) && + pmbus_check_word_register(client, 0, PMBUS_READ_FAN_SPEED_1)) { + info->func[0] |= PMBUS_HAVE_FAN12; + if (pmbus_check_byte_register(client, 0, PMBUS_STATUS_FAN_12)) + info->func[0] |= PMBUS_HAVE_STATUS_FAN12; + } + if (pmbus_check_byte_register(client, 0, PMBUS_FAN_CONFIG_34) && + pmbus_check_word_register(client, 0, PMBUS_READ_FAN_SPEED_3)) { + info->func[0] |= PMBUS_HAVE_FAN34; + if (pmbus_check_byte_register(client, 0, PMBUS_STATUS_FAN_34)) + info->func[0] |= PMBUS_HAVE_STATUS_FAN34; + } + if (pmbus_check_word_register(client, 0, PMBUS_READ_TEMPERATURE_1)) + info->func[0] |= PMBUS_HAVE_TEMP; + if (pmbus_check_word_register(client, 0, PMBUS_READ_TEMPERATURE_2)) + info->func[0] |= PMBUS_HAVE_TEMP2; + if (pmbus_check_word_register(client, 0, PMBUS_READ_TEMPERATURE_3)) + info->func[0] |= PMBUS_HAVE_TEMP3; + if (info->func[0] & (PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 + | PMBUS_HAVE_TEMP3) + && pmbus_check_byte_register(client, 0, + PMBUS_STATUS_TEMPERATURE)) + info->func[0] |= PMBUS_HAVE_STATUS_TEMP; + + /* Sensors detected on all pages */ + for (page = 0; page < info->pages; page++) { + if (pmbus_check_word_register(client, page, PMBUS_READ_VOUT)) { + info->func[page] |= PMBUS_HAVE_VOUT; + if (pmbus_check_byte_register(client, page, + PMBUS_STATUS_VOUT)) + info->func[page] |= PMBUS_HAVE_STATUS_VOUT; + } + if (pmbus_check_word_register(client, page, PMBUS_READ_IOUT)) { + info->func[page] |= PMBUS_HAVE_IOUT; + if (pmbus_check_byte_register(client, 0, + PMBUS_STATUS_IOUT)) + info->func[page] |= PMBUS_HAVE_STATUS_IOUT; + } + if (pmbus_check_word_register(client, page, PMBUS_READ_POUT)) + info->func[page] |= PMBUS_HAVE_POUT; + } +} + +/* + * Identify chip parameters. + */ +static int pmbus_identify(struct i2c_client *client, + struct pmbus_driver_info *info) +{ + int ret = 0; + + if (!info->pages) { + /* + * Check if the PAGE command is supported. If it is, + * keep setting the page number until it fails or until the + * maximum number of pages has been reached. Assume that + * this is the number of pages supported by the chip. + */ + if (pmbus_check_byte_register(client, 0, PMBUS_PAGE)) { + int page; + + for (page = 1; page < PMBUS_PAGES; page++) { + if (pmbus_set_page(client, page, 0xff) < 0) + break; + } + pmbus_set_page(client, 0, 0xff); + info->pages = page; + } else { + info->pages = 1; + } + + pmbus_clear_faults(client); + } + + if (pmbus_check_byte_register(client, 0, PMBUS_VOUT_MODE)) { + int vout_mode, i; + + vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE); + if (vout_mode >= 0 && vout_mode != 0xff) { + switch (vout_mode >> 5) { + case 0: + break; + case 1: + info->format[PSC_VOLTAGE_OUT] = vid; + for (i = 0; i < info->pages; i++) + info->vrm_version[i] = vr11; + break; + case 2: + info->format[PSC_VOLTAGE_OUT] = direct; + break; + default: + ret = -ENODEV; + goto abort; + } + } + } + + /* + * We should check if the COEFFICIENTS register is supported. + * If it is, and the chip is configured for direct mode, we can read + * the coefficients from the chip, one set per group of sensor + * registers. + * + * To do this, we will need access to a chip which actually supports the + * COEFFICIENTS command, since the command is too complex to implement + * without testing it. Until then, abort if a chip configured for direct + * mode was detected. + */ + if (info->format[PSC_VOLTAGE_OUT] == direct) { + ret = -ENODEV; + goto abort; + } + + /* Try to find sensor groups */ + pmbus_find_sensor_groups(client, info); +abort: + return ret; +} + +static int pmbus_probe(struct i2c_client *client) +{ + struct pmbus_driver_info *info; + struct pmbus_platform_data *pdata = NULL; + struct device *dev = &client->dev; + struct pmbus_device_info *device_info; + + info = devm_kzalloc(dev, sizeof(struct pmbus_driver_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + device_info = (struct pmbus_device_info *)i2c_match_id(pmbus_id, client)->driver_data; + if (device_info->flags) { + pdata = devm_kzalloc(dev, sizeof(struct pmbus_platform_data), + GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + pdata->flags = device_info->flags; + } + + info->pages = device_info->pages; + info->identify = pmbus_identify; + dev->platform_data = pdata; + + return pmbus_do_probe(client, info); +} + +static const struct pmbus_device_info pmbus_info_one = { + .pages = 1, + .flags = 0 +}; + +static const struct pmbus_device_info pmbus_info_zero = { + .pages = 0, + .flags = 0 +}; + +static const struct pmbus_device_info pmbus_info_one_skip = { + .pages = 1, + .flags = PMBUS_SKIP_STATUS_CHECK +}; + +static const struct pmbus_device_info pmbus_info_one_status = { + .pages = 1, + .flags = PMBUS_READ_STATUS_AFTER_FAILED_CHECK +}; + +/* + * Use driver_data to set the number of pages supported by the chip. + */ +static const struct i2c_device_id pmbus_id[] = { + {"adp4000", (kernel_ulong_t)&pmbus_info_one}, + {"bmr310", (kernel_ulong_t)&pmbus_info_one_status}, + {"bmr453", (kernel_ulong_t)&pmbus_info_one}, + {"bmr454", (kernel_ulong_t)&pmbus_info_one}, + {"bmr456", (kernel_ulong_t)&pmbus_info_one}, + {"bmr457", (kernel_ulong_t)&pmbus_info_one}, + {"bmr458", (kernel_ulong_t)&pmbus_info_one_status}, + {"bmr480", (kernel_ulong_t)&pmbus_info_one_status}, + {"bmr490", (kernel_ulong_t)&pmbus_info_one_status}, + {"bmr491", (kernel_ulong_t)&pmbus_info_one_status}, + {"bmr492", (kernel_ulong_t)&pmbus_info_one}, + {"dps460", (kernel_ulong_t)&pmbus_info_one_skip}, + {"dps650ab", (kernel_ulong_t)&pmbus_info_one_skip}, + {"dps800", (kernel_ulong_t)&pmbus_info_one_skip}, + {"max20796", (kernel_ulong_t)&pmbus_info_one}, + {"mdt040", (kernel_ulong_t)&pmbus_info_one}, + {"ncp4200", (kernel_ulong_t)&pmbus_info_one}, + {"ncp4208", (kernel_ulong_t)&pmbus_info_one}, + {"pdt003", (kernel_ulong_t)&pmbus_info_one}, + {"pdt006", (kernel_ulong_t)&pmbus_info_one}, + {"pdt012", (kernel_ulong_t)&pmbus_info_one}, + {"pmbus", (kernel_ulong_t)&pmbus_info_zero}, + {"sgd009", (kernel_ulong_t)&pmbus_info_one_skip}, + {"tps40400", (kernel_ulong_t)&pmbus_info_one}, + {"tps544b20", (kernel_ulong_t)&pmbus_info_one}, + {"tps544b25", (kernel_ulong_t)&pmbus_info_one}, + {"tps544c20", (kernel_ulong_t)&pmbus_info_one}, + {"tps544c25", (kernel_ulong_t)&pmbus_info_one}, + {"udt020", (kernel_ulong_t)&pmbus_info_one}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, pmbus_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver pmbus_driver = { + .driver = { + .name = "pmbus", + }, + .probe_new = pmbus_probe, + .id_table = pmbus_id, +}; + +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 new file mode 100644 index 000000000..10fb17879 --- /dev/null +++ b/drivers/hwmon/pmbus/pmbus.h @@ -0,0 +1,511 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * pmbus.h - Common defines and structures for PMBus devices + * + * Copyright (c) 2010, 2011 Ericsson AB. + * Copyright (c) 2012 Guenter Roeck + */ + +#ifndef PMBUS_H +#define PMBUS_H + +#include <linux/bitops.h> +#include <linux/regulator/driver.h> + +/* + * Registers + */ +enum pmbus_regs { + PMBUS_PAGE = 0x00, + PMBUS_OPERATION = 0x01, + PMBUS_ON_OFF_CONFIG = 0x02, + PMBUS_CLEAR_FAULTS = 0x03, + PMBUS_PHASE = 0x04, + + PMBUS_WRITE_PROTECT = 0x10, + + PMBUS_CAPABILITY = 0x19, + PMBUS_QUERY = 0x1A, + + PMBUS_VOUT_MODE = 0x20, + PMBUS_VOUT_COMMAND = 0x21, + PMBUS_VOUT_TRIM = 0x22, + PMBUS_VOUT_CAL_OFFSET = 0x23, + PMBUS_VOUT_MAX = 0x24, + PMBUS_VOUT_MARGIN_HIGH = 0x25, + PMBUS_VOUT_MARGIN_LOW = 0x26, + PMBUS_VOUT_TRANSITION_RATE = 0x27, + PMBUS_VOUT_DROOP = 0x28, + PMBUS_VOUT_SCALE_LOOP = 0x29, + PMBUS_VOUT_SCALE_MONITOR = 0x2A, + + PMBUS_COEFFICIENTS = 0x30, + PMBUS_POUT_MAX = 0x31, + + PMBUS_FAN_CONFIG_12 = 0x3A, + PMBUS_FAN_COMMAND_1 = 0x3B, + PMBUS_FAN_COMMAND_2 = 0x3C, + PMBUS_FAN_CONFIG_34 = 0x3D, + PMBUS_FAN_COMMAND_3 = 0x3E, + PMBUS_FAN_COMMAND_4 = 0x3F, + + PMBUS_VOUT_OV_FAULT_LIMIT = 0x40, + PMBUS_VOUT_OV_FAULT_RESPONSE = 0x41, + PMBUS_VOUT_OV_WARN_LIMIT = 0x42, + PMBUS_VOUT_UV_WARN_LIMIT = 0x43, + PMBUS_VOUT_UV_FAULT_LIMIT = 0x44, + PMBUS_VOUT_UV_FAULT_RESPONSE = 0x45, + PMBUS_IOUT_OC_FAULT_LIMIT = 0x46, + PMBUS_IOUT_OC_FAULT_RESPONSE = 0x47, + PMBUS_IOUT_OC_LV_FAULT_LIMIT = 0x48, + PMBUS_IOUT_OC_LV_FAULT_RESPONSE = 0x49, + PMBUS_IOUT_OC_WARN_LIMIT = 0x4A, + PMBUS_IOUT_UC_FAULT_LIMIT = 0x4B, + PMBUS_IOUT_UC_FAULT_RESPONSE = 0x4C, + + PMBUS_OT_FAULT_LIMIT = 0x4F, + PMBUS_OT_FAULT_RESPONSE = 0x50, + PMBUS_OT_WARN_LIMIT = 0x51, + PMBUS_UT_WARN_LIMIT = 0x52, + PMBUS_UT_FAULT_LIMIT = 0x53, + PMBUS_UT_FAULT_RESPONSE = 0x54, + PMBUS_VIN_OV_FAULT_LIMIT = 0x55, + PMBUS_VIN_OV_FAULT_RESPONSE = 0x56, + PMBUS_VIN_OV_WARN_LIMIT = 0x57, + PMBUS_VIN_UV_WARN_LIMIT = 0x58, + PMBUS_VIN_UV_FAULT_LIMIT = 0x59, + + PMBUS_IIN_OC_FAULT_LIMIT = 0x5B, + PMBUS_IIN_OC_WARN_LIMIT = 0x5D, + + PMBUS_POUT_OP_FAULT_LIMIT = 0x68, + PMBUS_POUT_OP_WARN_LIMIT = 0x6A, + PMBUS_PIN_OP_WARN_LIMIT = 0x6B, + + PMBUS_STATUS_BYTE = 0x78, + PMBUS_STATUS_WORD = 0x79, + PMBUS_STATUS_VOUT = 0x7A, + PMBUS_STATUS_IOUT = 0x7B, + PMBUS_STATUS_INPUT = 0x7C, + PMBUS_STATUS_TEMPERATURE = 0x7D, + PMBUS_STATUS_CML = 0x7E, + PMBUS_STATUS_OTHER = 0x7F, + PMBUS_STATUS_MFR_SPECIFIC = 0x80, + PMBUS_STATUS_FAN_12 = 0x81, + PMBUS_STATUS_FAN_34 = 0x82, + + PMBUS_READ_VIN = 0x88, + PMBUS_READ_IIN = 0x89, + PMBUS_READ_VCAP = 0x8A, + PMBUS_READ_VOUT = 0x8B, + PMBUS_READ_IOUT = 0x8C, + PMBUS_READ_TEMPERATURE_1 = 0x8D, + PMBUS_READ_TEMPERATURE_2 = 0x8E, + PMBUS_READ_TEMPERATURE_3 = 0x8F, + PMBUS_READ_FAN_SPEED_1 = 0x90, + PMBUS_READ_FAN_SPEED_2 = 0x91, + PMBUS_READ_FAN_SPEED_3 = 0x92, + PMBUS_READ_FAN_SPEED_4 = 0x93, + PMBUS_READ_DUTY_CYCLE = 0x94, + PMBUS_READ_FREQUENCY = 0x95, + PMBUS_READ_POUT = 0x96, + PMBUS_READ_PIN = 0x97, + + PMBUS_REVISION = 0x98, + PMBUS_MFR_ID = 0x99, + PMBUS_MFR_MODEL = 0x9A, + PMBUS_MFR_REVISION = 0x9B, + PMBUS_MFR_LOCATION = 0x9C, + PMBUS_MFR_DATE = 0x9D, + PMBUS_MFR_SERIAL = 0x9E, + + PMBUS_MFR_VIN_MIN = 0xA0, + PMBUS_MFR_VIN_MAX = 0xA1, + PMBUS_MFR_IIN_MAX = 0xA2, + PMBUS_MFR_PIN_MAX = 0xA3, + PMBUS_MFR_VOUT_MIN = 0xA4, + PMBUS_MFR_VOUT_MAX = 0xA5, + PMBUS_MFR_IOUT_MAX = 0xA6, + PMBUS_MFR_POUT_MAX = 0xA7, + + PMBUS_IC_DEVICE_ID = 0xAD, + PMBUS_IC_DEVICE_REV = 0xAE, + + PMBUS_MFR_MAX_TEMP_1 = 0xC0, + PMBUS_MFR_MAX_TEMP_2 = 0xC1, + PMBUS_MFR_MAX_TEMP_3 = 0xC2, + +/* + * Virtual registers. + * Useful to support attributes which are not supported by standard PMBus + * registers but exist as manufacturer specific registers on individual chips. + * Must be mapped to real registers in device specific code. + * + * Semantics: + * Virtual registers are all word size. + * READ registers are read-only; writes are either ignored or return an error. + * RESET registers are read/write. Reading reset registers returns zero + * (used for detection), writing any value causes the associated history to be + * reset. + * Virtual registers have to be handled in device specific driver code. Chip + * driver code returns non-negative register values if a virtual register is + * supported, or a negative error code if not. The chip driver may return + * -ENODATA or any other error code in this case, though an error code other + * than -ENODATA is handled more efficiently and thus preferred. Either case, + * the calling PMBus core code will abort if the chip driver returns an error + * code when reading or writing virtual registers. + */ + PMBUS_VIRT_BASE = 0x100, + PMBUS_VIRT_READ_TEMP_AVG, + PMBUS_VIRT_READ_TEMP_MIN, + PMBUS_VIRT_READ_TEMP_MAX, + PMBUS_VIRT_RESET_TEMP_HISTORY, + PMBUS_VIRT_READ_VIN_AVG, + PMBUS_VIRT_READ_VIN_MIN, + PMBUS_VIRT_READ_VIN_MAX, + PMBUS_VIRT_RESET_VIN_HISTORY, + PMBUS_VIRT_READ_IIN_AVG, + PMBUS_VIRT_READ_IIN_MIN, + PMBUS_VIRT_READ_IIN_MAX, + PMBUS_VIRT_RESET_IIN_HISTORY, + PMBUS_VIRT_READ_PIN_AVG, + PMBUS_VIRT_READ_PIN_MIN, + PMBUS_VIRT_READ_PIN_MAX, + PMBUS_VIRT_RESET_PIN_HISTORY, + PMBUS_VIRT_READ_POUT_AVG, + PMBUS_VIRT_READ_POUT_MIN, + PMBUS_VIRT_READ_POUT_MAX, + PMBUS_VIRT_RESET_POUT_HISTORY, + PMBUS_VIRT_READ_VOUT_AVG, + PMBUS_VIRT_READ_VOUT_MIN, + PMBUS_VIRT_READ_VOUT_MAX, + PMBUS_VIRT_RESET_VOUT_HISTORY, + PMBUS_VIRT_READ_IOUT_AVG, + PMBUS_VIRT_READ_IOUT_MIN, + PMBUS_VIRT_READ_IOUT_MAX, + PMBUS_VIRT_RESET_IOUT_HISTORY, + PMBUS_VIRT_READ_TEMP2_AVG, + PMBUS_VIRT_READ_TEMP2_MIN, + PMBUS_VIRT_READ_TEMP2_MAX, + PMBUS_VIRT_RESET_TEMP2_HISTORY, + + PMBUS_VIRT_READ_VMON, + PMBUS_VIRT_VMON_UV_WARN_LIMIT, + PMBUS_VIRT_VMON_OV_WARN_LIMIT, + PMBUS_VIRT_VMON_UV_FAULT_LIMIT, + PMBUS_VIRT_VMON_OV_FAULT_LIMIT, + PMBUS_VIRT_STATUS_VMON, + + /* + * RPM and PWM Fan control + * + * Drivers wanting to expose PWM control must define the behaviour of + * PMBUS_VIRT_PWM_[1-4] and PMBUS_VIRT_PWM_ENABLE_[1-4] in the + * {read,write}_word_data callback. + * + * pmbus core provides a default implementation for + * PMBUS_VIRT_FAN_TARGET_[1-4]. + * + * TARGET, PWM and PWM_ENABLE members must be defined sequentially; + * pmbus core uses the difference between the provided register and + * it's _1 counterpart to calculate the FAN/PWM ID. + */ + PMBUS_VIRT_FAN_TARGET_1, + PMBUS_VIRT_FAN_TARGET_2, + PMBUS_VIRT_FAN_TARGET_3, + PMBUS_VIRT_FAN_TARGET_4, + PMBUS_VIRT_PWM_1, + PMBUS_VIRT_PWM_2, + PMBUS_VIRT_PWM_3, + PMBUS_VIRT_PWM_4, + PMBUS_VIRT_PWM_ENABLE_1, + PMBUS_VIRT_PWM_ENABLE_2, + PMBUS_VIRT_PWM_ENABLE_3, + PMBUS_VIRT_PWM_ENABLE_4, + + /* Samples for average + * + * Drivers wanting to expose functionality for changing the number of + * samples used for average values should implement support in + * {read,write}_word_data callback for either PMBUS_VIRT_SAMPLES if it + * applies to all types of measurements, or any number of specific + * PMBUS_VIRT_*_SAMPLES registers to allow for individual control. + */ + PMBUS_VIRT_SAMPLES, + PMBUS_VIRT_IN_SAMPLES, + PMBUS_VIRT_CURR_SAMPLES, + PMBUS_VIRT_POWER_SAMPLES, + PMBUS_VIRT_TEMP_SAMPLES, +}; + +/* + * OPERATION + */ +#define PB_OPERATION_CONTROL_ON BIT(7) + +/* + * WRITE_PROTECT + */ +#define PB_WP_ALL BIT(7) /* all but WRITE_PROTECT */ +#define PB_WP_OP BIT(6) /* all but WP, OPERATION, PAGE */ +#define PB_WP_VOUT BIT(5) /* all but WP, OPERATION, PAGE, VOUT, ON_OFF */ + +#define PB_WP_ANY (PB_WP_ALL | PB_WP_OP | PB_WP_VOUT) + +/* + * CAPABILITY + */ +#define PB_CAPABILITY_SMBALERT BIT(4) +#define PB_CAPABILITY_ERROR_CHECK BIT(7) + +/* + * VOUT_MODE + */ +#define PB_VOUT_MODE_MODE_MASK 0xe0 +#define PB_VOUT_MODE_PARAM_MASK 0x1f + +#define PB_VOUT_MODE_LINEAR 0x00 +#define PB_VOUT_MODE_VID 0x20 +#define PB_VOUT_MODE_DIRECT 0x40 + +/* + * Fan configuration + */ +#define PB_FAN_2_PULSE_MASK (BIT(0) | BIT(1)) +#define PB_FAN_2_RPM BIT(2) +#define PB_FAN_2_INSTALLED BIT(3) +#define PB_FAN_1_PULSE_MASK (BIT(4) | BIT(5)) +#define PB_FAN_1_RPM BIT(6) +#define PB_FAN_1_INSTALLED BIT(7) + +enum pmbus_fan_mode { percent = 0, rpm }; + +/* + * STATUS_BYTE, STATUS_WORD (lower) + */ +#define PB_STATUS_NONE_ABOVE BIT(0) +#define PB_STATUS_CML BIT(1) +#define PB_STATUS_TEMPERATURE BIT(2) +#define PB_STATUS_VIN_UV BIT(3) +#define PB_STATUS_IOUT_OC BIT(4) +#define PB_STATUS_VOUT_OV BIT(5) +#define PB_STATUS_OFF BIT(6) +#define PB_STATUS_BUSY BIT(7) + +/* + * STATUS_WORD (upper) + */ +#define PB_STATUS_UNKNOWN BIT(8) +#define PB_STATUS_OTHER BIT(9) +#define PB_STATUS_FANS BIT(10) +#define PB_STATUS_POWER_GOOD_N BIT(11) +#define PB_STATUS_WORD_MFR BIT(12) +#define PB_STATUS_INPUT BIT(13) +#define PB_STATUS_IOUT_POUT BIT(14) +#define PB_STATUS_VOUT BIT(15) + +/* + * STATUS_IOUT + */ +#define PB_POUT_OP_WARNING BIT(0) +#define PB_POUT_OP_FAULT BIT(1) +#define PB_POWER_LIMITING BIT(2) +#define PB_CURRENT_SHARE_FAULT BIT(3) +#define PB_IOUT_UC_FAULT BIT(4) +#define PB_IOUT_OC_WARNING BIT(5) +#define PB_IOUT_OC_LV_FAULT BIT(6) +#define PB_IOUT_OC_FAULT BIT(7) + +/* + * STATUS_VOUT, STATUS_INPUT + */ +#define PB_VOLTAGE_VIN_OFF BIT(3) +#define PB_VOLTAGE_UV_FAULT BIT(4) +#define PB_VOLTAGE_UV_WARNING BIT(5) +#define PB_VOLTAGE_OV_WARNING BIT(6) +#define PB_VOLTAGE_OV_FAULT BIT(7) + +/* + * STATUS_INPUT + */ +#define PB_PIN_OP_WARNING BIT(0) +#define PB_IIN_OC_WARNING BIT(1) +#define PB_IIN_OC_FAULT BIT(2) + +/* + * STATUS_TEMPERATURE + */ +#define PB_TEMP_UT_FAULT BIT(4) +#define PB_TEMP_UT_WARNING BIT(5) +#define PB_TEMP_OT_WARNING BIT(6) +#define PB_TEMP_OT_FAULT BIT(7) + +/* + * STATUS_FAN + */ +#define PB_FAN_AIRFLOW_WARNING BIT(0) +#define PB_FAN_AIRFLOW_FAULT BIT(1) +#define PB_FAN_FAN2_SPEED_OVERRIDE BIT(2) +#define PB_FAN_FAN1_SPEED_OVERRIDE BIT(3) +#define PB_FAN_FAN2_WARNING BIT(4) +#define PB_FAN_FAN1_WARNING BIT(5) +#define PB_FAN_FAN2_FAULT BIT(6) +#define PB_FAN_FAN1_FAULT BIT(7) + +/* + * CML_FAULT_STATUS + */ +#define PB_CML_FAULT_OTHER_MEM_LOGIC BIT(0) +#define PB_CML_FAULT_OTHER_COMM BIT(1) +#define PB_CML_FAULT_PROCESSOR BIT(3) +#define PB_CML_FAULT_MEMORY BIT(4) +#define PB_CML_FAULT_PACKET_ERROR BIT(5) +#define PB_CML_FAULT_INVALID_DATA BIT(6) +#define PB_CML_FAULT_INVALID_COMMAND BIT(7) + +enum pmbus_sensor_classes { + PSC_VOLTAGE_IN = 0, + PSC_VOLTAGE_OUT, + PSC_CURRENT_IN, + PSC_CURRENT_OUT, + PSC_POWER, + PSC_TEMPERATURE, + PSC_FAN, + PSC_PWM, + PSC_NUM_CLASSES /* Number of power sensor classes */ +}; + +#define PMBUS_PAGES 32 /* Per PMBus specification */ +#define PMBUS_PHASES 10 /* Maximum number of phases per page */ + +/* Functionality bit mask */ +#define PMBUS_HAVE_VIN BIT(0) +#define PMBUS_HAVE_VCAP BIT(1) +#define PMBUS_HAVE_VOUT BIT(2) +#define PMBUS_HAVE_IIN BIT(3) +#define PMBUS_HAVE_IOUT BIT(4) +#define PMBUS_HAVE_PIN BIT(5) +#define PMBUS_HAVE_POUT BIT(6) +#define PMBUS_HAVE_FAN12 BIT(7) +#define PMBUS_HAVE_FAN34 BIT(8) +#define PMBUS_HAVE_TEMP BIT(9) +#define PMBUS_HAVE_TEMP2 BIT(10) +#define PMBUS_HAVE_TEMP3 BIT(11) +#define PMBUS_HAVE_STATUS_VOUT BIT(12) +#define PMBUS_HAVE_STATUS_IOUT BIT(13) +#define PMBUS_HAVE_STATUS_INPUT BIT(14) +#define PMBUS_HAVE_STATUS_TEMP BIT(15) +#define PMBUS_HAVE_STATUS_FAN12 BIT(16) +#define PMBUS_HAVE_STATUS_FAN34 BIT(17) +#define PMBUS_HAVE_VMON BIT(18) +#define PMBUS_HAVE_STATUS_VMON BIT(19) +#define PMBUS_HAVE_PWM12 BIT(20) +#define PMBUS_HAVE_PWM34 BIT(21) +#define PMBUS_HAVE_SAMPLES BIT(22) + +#define PMBUS_PHASE_VIRTUAL BIT(30) /* Phases on this page are virtual */ +#define PMBUS_PAGE_VIRTUAL BIT(31) /* Page is virtual */ + +enum pmbus_data_format { linear = 0, ieee754, direct, vid }; +enum vrm_version { vr11 = 0, vr12, vr13, imvp9, amd625mv }; + +struct pmbus_driver_info { + int pages; /* Total number of pages */ + u8 phases[PMBUS_PAGES]; /* Number of phases per page */ + enum pmbus_data_format format[PSC_NUM_CLASSES]; + enum vrm_version vrm_version[PMBUS_PAGES]; /* vrm version per page */ + /* + * Support one set of coefficients for each sensor type + * Used for chips providing data in direct mode. + */ + int m[PSC_NUM_CLASSES]; /* mantissa for direct data format */ + int b[PSC_NUM_CLASSES]; /* offset */ + int R[PSC_NUM_CLASSES]; /* exponent */ + + u32 func[PMBUS_PAGES]; /* Functionality, per page */ + u32 pfunc[PMBUS_PHASES];/* Functionality, per phase */ + /* + * The following functions map manufacturing specific register values + * to PMBus standard register values. Specify only if mapping is + * necessary. + * Functions return the register value (read) or zero (write) if + * successful. A return value of -ENODATA indicates that there is no + * manufacturer specific register, but that a standard PMBus register + * may exist. Any other negative return value indicates that the + * register does not exist, and that no attempt should be made to read + * the standard register. + */ + int (*read_byte_data)(struct i2c_client *client, int page, int reg); + int (*read_word_data)(struct i2c_client *client, int page, int phase, + int reg); + int (*write_byte_data)(struct i2c_client *client, int page, int reg, + u8 byte); + int (*write_word_data)(struct i2c_client *client, int page, int reg, + u16 word); + int (*write_byte)(struct i2c_client *client, int page, u8 value); + /* + * The identify function determines supported PMBus functionality. + * This function is only necessary if a chip driver supports multiple + * chips, and the chip functionality is not pre-determined. + */ + int (*identify)(struct i2c_client *client, + struct pmbus_driver_info *info); + + /* Regulator functionality, if supported by this chip driver. */ + int num_regulators; + const struct regulator_desc *reg_desc; + + /* custom attributes */ + const struct attribute_group **groups; +}; + +/* Regulator ops */ + +extern const struct regulator_ops pmbus_regulator_ops; + +/* Macros for filling in array of struct regulator_desc */ +#define PMBUS_REGULATOR_STEP(_name, _id, _voltages, _step) \ + [_id] = { \ + .name = (_name # _id), \ + .id = (_id), \ + .of_match = of_match_ptr(_name # _id), \ + .regulators_node = of_match_ptr("regulators"), \ + .ops = &pmbus_regulator_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .n_voltages = _voltages, \ + .uV_step = _step, \ + } + +#define PMBUS_REGULATOR(_name, _id) PMBUS_REGULATOR_STEP(_name, _id, 0, 0) + +/* 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); +int pmbus_write_word_data(struct i2c_client *client, int page, u8 reg, + u16 word); +int pmbus_read_byte_data(struct i2c_client *client, int page, u8 reg); +int pmbus_write_byte(struct i2c_client *client, int page, u8 value); +int pmbus_write_byte_data(struct i2c_client *client, int page, u8 reg, + u8 value); +int pmbus_update_byte_data(struct i2c_client *client, int page, u8 reg, + u8 mask, u8 value); +void pmbus_clear_faults(struct i2c_client *client); +bool pmbus_check_byte_register(struct i2c_client *client, int page, int reg); +bool pmbus_check_word_register(struct i2c_client *client, int page, int reg); +int pmbus_do_probe(struct i2c_client *client, struct pmbus_driver_info *info); +const struct pmbus_driver_info *pmbus_get_driver_info(struct i2c_client + *client); +int pmbus_get_fan_rate_device(struct i2c_client *client, int page, int id, + enum pmbus_fan_mode mode); +int pmbus_get_fan_rate_cached(struct i2c_client *client, int page, int id, + enum pmbus_fan_mode mode); +int pmbus_update_fan(struct i2c_client *client, int page, int id, + u8 config, u8 mask, u16 command); +struct dentry *pmbus_get_debugfs_dir(struct i2c_client *client); + +#endif /* PMBUS_H */ diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c new file mode 100644 index 000000000..7ec049347 --- /dev/null +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -0,0 +1,3431 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for PMBus devices + * + * Copyright (c) 2010, 2011 Ericsson AB. + * Copyright (c) 2012 Guenter Roeck + */ + +#include <linux/debugfs.h> +#include <linux/kernel.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/pmbus.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/of.h> +#include <linux/thermal.h> +#include "pmbus.h" + +/* + * Number of additional attribute pointers to allocate + * with each call to krealloc + */ +#define PMBUS_ATTR_ALLOC_SIZE 32 +#define PMBUS_NAME_SIZE 24 + +struct pmbus_sensor { + struct pmbus_sensor *next; + char name[PMBUS_NAME_SIZE]; /* sysfs sensor name */ + struct device_attribute attribute; + u8 page; /* page number */ + u8 phase; /* phase number, 0xff for all phases */ + u16 reg; /* register */ + enum pmbus_sensor_classes class; /* sensor class */ + bool update; /* runtime sensor update needed */ + bool convert; /* Whether or not to apply linear/vid/direct */ + int data; /* Sensor data. + Negative if there was a read error */ +}; +#define to_pmbus_sensor(_attr) \ + container_of(_attr, struct pmbus_sensor, attribute) + +struct pmbus_boolean { + char name[PMBUS_NAME_SIZE]; /* sysfs boolean name */ + struct sensor_device_attribute attribute; + struct pmbus_sensor *s1; + struct pmbus_sensor *s2; +}; +#define to_pmbus_boolean(_attr) \ + container_of(_attr, struct pmbus_boolean, attribute) + +struct pmbus_label { + char name[PMBUS_NAME_SIZE]; /* sysfs label name */ + struct device_attribute attribute; + char label[PMBUS_NAME_SIZE]; /* label */ +}; +#define to_pmbus_label(_attr) \ + container_of(_attr, struct pmbus_label, attribute) + +/* Macros for converting between sensor index and register/page/status mask */ + +#define PB_STATUS_MASK 0xffff +#define PB_REG_SHIFT 16 +#define PB_REG_MASK 0x3ff +#define PB_PAGE_SHIFT 26 +#define PB_PAGE_MASK 0x3f + +#define pb_reg_to_index(page, reg, mask) (((page) << PB_PAGE_SHIFT) | \ + ((reg) << PB_REG_SHIFT) | (mask)) + +#define pb_index_to_page(index) (((index) >> PB_PAGE_SHIFT) & PB_PAGE_MASK) +#define pb_index_to_reg(index) (((index) >> PB_REG_SHIFT) & PB_REG_MASK) +#define pb_index_to_mask(index) ((index) & PB_STATUS_MASK) + +struct pmbus_data { + struct device *dev; + struct device *hwmon_dev; + + u32 flags; /* from platform data */ + + int exponent[PMBUS_PAGES]; + /* linear mode: exponent for output voltages */ + + const struct pmbus_driver_info *info; + + int max_attributes; + int num_attributes; + struct attribute_group group; + const struct attribute_group **groups; + struct dentry *debugfs; /* debugfs device directory */ + + struct pmbus_sensor *sensors; + + struct mutex update_lock; + + bool has_status_word; /* device uses STATUS_WORD register */ + int (*read_status)(struct i2c_client *client, int page); + + s16 currpage; /* current page, -1 for unknown/unset */ + s16 currphase; /* current phase, 0xff for all, -1 for unknown/unset */ + + int vout_low[PMBUS_PAGES]; /* voltage low margin */ + int vout_high[PMBUS_PAGES]; /* voltage high margin */ +}; + +struct pmbus_debugfs_entry { + struct i2c_client *client; + u8 page; + u8 reg; +}; + +static const int pmbus_fan_rpm_mask[] = { + PB_FAN_1_RPM, + PB_FAN_2_RPM, + PB_FAN_1_RPM, + PB_FAN_2_RPM, +}; + +static const int pmbus_fan_config_registers[] = { + PMBUS_FAN_CONFIG_12, + PMBUS_FAN_CONFIG_12, + PMBUS_FAN_CONFIG_34, + PMBUS_FAN_CONFIG_34 +}; + +static const int pmbus_fan_command_registers[] = { + PMBUS_FAN_COMMAND_1, + PMBUS_FAN_COMMAND_2, + PMBUS_FAN_COMMAND_3, + PMBUS_FAN_COMMAND_4, +}; + +void pmbus_clear_cache(struct i2c_client *client) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + struct pmbus_sensor *sensor; + + for (sensor = data->sensors; sensor; sensor = sensor->next) + sensor->data = -ENODATA; +} +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) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + int rv; + + if (page < 0) + return 0; + + if (!(data->info->func[page] & PMBUS_PAGE_VIRTUAL) && + data->info->pages > 1 && page != data->currpage) { + rv = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page); + if (rv < 0) + return rv; + + rv = i2c_smbus_read_byte_data(client, PMBUS_PAGE); + if (rv < 0) + return rv; + + if (rv != page) + return -EIO; + } + data->currpage = page; + + if (data->info->phases[page] && data->currphase != phase && + !(data->info->func[page] & PMBUS_PHASE_VIRTUAL)) { + rv = i2c_smbus_write_byte_data(client, PMBUS_PHASE, + phase); + if (rv) + return rv; + } + data->currphase = phase; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(pmbus_set_page, PMBUS); + +int pmbus_write_byte(struct i2c_client *client, int page, u8 value) +{ + int rv; + + rv = pmbus_set_page(client, page, 0xff); + if (rv < 0) + return rv; + + return i2c_smbus_write_byte(client, value); +} +EXPORT_SYMBOL_NS_GPL(pmbus_write_byte, PMBUS); + +/* + * _pmbus_write_byte() is similar to pmbus_write_byte(), but checks if + * a device specific mapping function exists and calls it if necessary. + */ +static int _pmbus_write_byte(struct i2c_client *client, int page, u8 value) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + const struct pmbus_driver_info *info = data->info; + int status; + + if (info->write_byte) { + status = info->write_byte(client, page, value); + if (status != -ENODATA) + return status; + } + return pmbus_write_byte(client, page, value); +} + +int pmbus_write_word_data(struct i2c_client *client, int page, u8 reg, + u16 word) +{ + int rv; + + rv = pmbus_set_page(client, page, 0xff); + if (rv < 0) + return rv; + + return i2c_smbus_write_word_data(client, reg, word); +} +EXPORT_SYMBOL_NS_GPL(pmbus_write_word_data, PMBUS); + + +static int pmbus_write_virt_reg(struct i2c_client *client, int page, int reg, + u16 word) +{ + int bit; + int id; + int rv; + + switch (reg) { + case PMBUS_VIRT_FAN_TARGET_1 ... PMBUS_VIRT_FAN_TARGET_4: + id = reg - PMBUS_VIRT_FAN_TARGET_1; + bit = pmbus_fan_rpm_mask[id]; + rv = pmbus_update_fan(client, page, id, bit, bit, word); + break; + default: + rv = -ENXIO; + break; + } + + return rv; +} + +/* + * _pmbus_write_word_data() is similar to pmbus_write_word_data(), but checks if + * a device specific mapping function exists and calls it if necessary. + */ +static int _pmbus_write_word_data(struct i2c_client *client, int page, int reg, + u16 word) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + const struct pmbus_driver_info *info = data->info; + int status; + + if (info->write_word_data) { + status = info->write_word_data(client, page, reg, word); + if (status != -ENODATA) + return status; + } + + if (reg >= PMBUS_VIRT_BASE) + return pmbus_write_virt_reg(client, page, reg, word); + + return pmbus_write_word_data(client, page, reg, word); +} + +/* + * _pmbus_write_byte_data() is similar to pmbus_write_byte_data(), but checks if + * a device specific mapping function exists and calls it if necessary. + */ +static int _pmbus_write_byte_data(struct i2c_client *client, int page, int reg, u8 value) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + const struct pmbus_driver_info *info = data->info; + int status; + + if (info->write_byte_data) { + status = info->write_byte_data(client, page, reg, value); + if (status != -ENODATA) + return status; + } + return pmbus_write_byte_data(client, page, reg, value); +} + +/* + * _pmbus_read_byte_data() is similar to pmbus_read_byte_data(), but checks if + * a device specific mapping function exists and calls it if necessary. + */ +static int _pmbus_read_byte_data(struct i2c_client *client, int page, int reg) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + const struct pmbus_driver_info *info = data->info; + int status; + + if (info->read_byte_data) { + status = info->read_byte_data(client, page, reg); + if (status != -ENODATA) + return status; + } + return pmbus_read_byte_data(client, page, reg); +} + +int pmbus_update_fan(struct i2c_client *client, int page, int id, + u8 config, u8 mask, u16 command) +{ + int from; + int rv; + u8 to; + + from = _pmbus_read_byte_data(client, page, + pmbus_fan_config_registers[id]); + if (from < 0) + return from; + + to = (from & ~mask) | (config & mask); + if (to != from) { + rv = _pmbus_write_byte_data(client, page, + pmbus_fan_config_registers[id], to); + if (rv < 0) + return rv; + } + + return _pmbus_write_word_data(client, page, + pmbus_fan_command_registers[id], command); +} +EXPORT_SYMBOL_NS_GPL(pmbus_update_fan, PMBUS); + +int pmbus_read_word_data(struct i2c_client *client, int page, int phase, u8 reg) +{ + int rv; + + rv = pmbus_set_page(client, page, phase); + if (rv < 0) + return rv; + + return i2c_smbus_read_word_data(client, reg); +} +EXPORT_SYMBOL_NS_GPL(pmbus_read_word_data, PMBUS); + +static int pmbus_read_virt_reg(struct i2c_client *client, int page, int reg) +{ + int rv; + int id; + + switch (reg) { + case PMBUS_VIRT_FAN_TARGET_1 ... PMBUS_VIRT_FAN_TARGET_4: + id = reg - PMBUS_VIRT_FAN_TARGET_1; + rv = pmbus_get_fan_rate_device(client, page, id, rpm); + break; + default: + rv = -ENXIO; + break; + } + + return rv; +} + +/* + * _pmbus_read_word_data() is similar to pmbus_read_word_data(), but checks if + * a device specific mapping function exists and calls it if necessary. + */ +static int _pmbus_read_word_data(struct i2c_client *client, int page, + int phase, int reg) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + const struct pmbus_driver_info *info = data->info; + int status; + + if (info->read_word_data) { + status = info->read_word_data(client, page, phase, reg); + if (status != -ENODATA) + return status; + } + + if (reg >= PMBUS_VIRT_BASE) + return pmbus_read_virt_reg(client, page, reg); + + return pmbus_read_word_data(client, page, phase, reg); +} + +/* Same as above, but without phase parameter, for use in check functions */ +static int __pmbus_read_word_data(struct i2c_client *client, int page, int reg) +{ + return _pmbus_read_word_data(client, page, 0xff, reg); +} + +int pmbus_read_byte_data(struct i2c_client *client, int page, u8 reg) +{ + int rv; + + rv = pmbus_set_page(client, page, 0xff); + if (rv < 0) + return rv; + + return i2c_smbus_read_byte_data(client, reg); +} +EXPORT_SYMBOL_NS_GPL(pmbus_read_byte_data, PMBUS); + +int pmbus_write_byte_data(struct i2c_client *client, int page, u8 reg, u8 value) +{ + int rv; + + rv = pmbus_set_page(client, page, 0xff); + if (rv < 0) + return rv; + + return i2c_smbus_write_byte_data(client, reg, value); +} +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) +{ + unsigned int tmp; + int rv; + + rv = _pmbus_read_byte_data(client, page, reg); + if (rv < 0) + return rv; + + tmp = (rv & ~mask) | (value & mask); + + if (tmp != rv) + rv = _pmbus_write_byte_data(client, page, reg, tmp); + + return rv; +} +EXPORT_SYMBOL_NS_GPL(pmbus_update_byte_data, PMBUS); + +static int pmbus_read_block_data(struct i2c_client *client, int page, u8 reg, + char *data_buf) +{ + int rv; + + rv = pmbus_set_page(client, page, 0xff); + if (rv < 0) + return rv; + + return i2c_smbus_read_block_data(client, reg, data_buf); +} + +static struct pmbus_sensor *pmbus_find_sensor(struct pmbus_data *data, int page, + int reg) +{ + struct pmbus_sensor *sensor; + + for (sensor = data->sensors; sensor; sensor = sensor->next) { + if (sensor->page == page && sensor->reg == reg) + return sensor; + } + + return ERR_PTR(-EINVAL); +} + +static int pmbus_get_fan_rate(struct i2c_client *client, int page, int id, + enum pmbus_fan_mode mode, + bool from_cache) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + bool want_rpm, have_rpm; + struct pmbus_sensor *s; + int config; + int reg; + + want_rpm = (mode == rpm); + + if (from_cache) { + reg = want_rpm ? PMBUS_VIRT_FAN_TARGET_1 : PMBUS_VIRT_PWM_1; + s = pmbus_find_sensor(data, page, reg + id); + if (IS_ERR(s)) + return PTR_ERR(s); + + return s->data; + } + + config = _pmbus_read_byte_data(client, page, + pmbus_fan_config_registers[id]); + if (config < 0) + return config; + + have_rpm = !!(config & pmbus_fan_rpm_mask[id]); + if (want_rpm == have_rpm) + return pmbus_read_word_data(client, page, 0xff, + pmbus_fan_command_registers[id]); + + /* Can't sensibly map between RPM and PWM, just return zero */ + return 0; +} + +int pmbus_get_fan_rate_device(struct i2c_client *client, int page, int id, + enum pmbus_fan_mode mode) +{ + return pmbus_get_fan_rate(client, page, id, mode, false); +} +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_NS_GPL(pmbus_get_fan_rate_cached, PMBUS); + +static void pmbus_clear_fault_page(struct i2c_client *client, int page) +{ + _pmbus_write_byte(client, page, PMBUS_CLEAR_FAULTS); +} + +void pmbus_clear_faults(struct i2c_client *client) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + int i; + + for (i = 0; i < data->info->pages; i++) + pmbus_clear_fault_page(client, i); +} +EXPORT_SYMBOL_NS_GPL(pmbus_clear_faults, PMBUS); + +static int pmbus_check_status_cml(struct i2c_client *client) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + int status, status2; + + status = data->read_status(client, -1); + if (status < 0 || (status & PB_STATUS_CML)) { + status2 = _pmbus_read_byte_data(client, -1, PMBUS_STATUS_CML); + if (status2 < 0 || (status2 & PB_CML_FAULT_INVALID_COMMAND)) + return -EIO; + } + return 0; +} + +static bool pmbus_check_register(struct i2c_client *client, + int (*func)(struct i2c_client *client, + int page, int reg), + int page, int reg) +{ + int rv; + struct pmbus_data *data = i2c_get_clientdata(client); + + rv = func(client, page, reg); + if (rv >= 0 && !(data->flags & PMBUS_SKIP_STATUS_CHECK)) + rv = pmbus_check_status_cml(client); + if (rv < 0 && (data->flags & PMBUS_READ_STATUS_AFTER_FAILED_CHECK)) + data->read_status(client, -1); + pmbus_clear_fault_page(client, -1); + return rv >= 0; +} + +static bool pmbus_check_status_register(struct i2c_client *client, int page) +{ + int status; + struct pmbus_data *data = i2c_get_clientdata(client); + + status = data->read_status(client, page); + if (status >= 0 && !(data->flags & PMBUS_SKIP_STATUS_CHECK) && + (status & PB_STATUS_CML)) { + status = _pmbus_read_byte_data(client, -1, PMBUS_STATUS_CML); + if (status < 0 || (status & PB_CML_FAULT_INVALID_COMMAND)) + status = -EIO; + } + + pmbus_clear_fault_page(client, -1); + return status >= 0; +} + +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_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_NS_GPL(pmbus_check_word_register, PMBUS); + +static bool __maybe_unused pmbus_check_block_register(struct i2c_client *client, + int page, int reg) +{ + int rv; + struct pmbus_data *data = i2c_get_clientdata(client); + char data_buf[I2C_SMBUS_BLOCK_MAX + 2]; + + rv = pmbus_read_block_data(client, page, reg, data_buf); + if (rv >= 0 && !(data->flags & PMBUS_SKIP_STATUS_CHECK)) + rv = pmbus_check_status_cml(client); + if (rv < 0 && (data->flags & PMBUS_READ_STATUS_AFTER_FAILED_CHECK)) + data->read_status(client, -1); + pmbus_clear_fault_page(client, -1); + return rv >= 0; +} + +const struct pmbus_driver_info *pmbus_get_driver_info(struct i2c_client *client) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + + return data->info; +} +EXPORT_SYMBOL_NS_GPL(pmbus_get_driver_info, PMBUS); + +static int pmbus_get_status(struct i2c_client *client, int page, int reg) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + int status; + + switch (reg) { + case PMBUS_STATUS_WORD: + status = data->read_status(client, page); + break; + default: + status = _pmbus_read_byte_data(client, page, reg); + break; + } + if (status < 0) + pmbus_clear_faults(client); + return status; +} + +static void pmbus_update_sensor_data(struct i2c_client *client, struct pmbus_sensor *sensor) +{ + if (sensor->data < 0 || sensor->update) + sensor->data = _pmbus_read_word_data(client, sensor->page, + sensor->phase, sensor->reg); +} + +/* + * Convert ieee754 sensor values to milli- or micro-units + * depending on sensor type. + * + * ieee754 data format: + * bit 15: sign + * bit 10..14: exponent + * bit 0..9: mantissa + * exponent=0: + * v=(−1)^signbit * 2^(−14) * 0.significantbits + * exponent=1..30: + * v=(−1)^signbit * 2^(exponent - 15) * 1.significantbits + * exponent=31: + * v=NaN + * + * Add the number mantissa bits into the calculations for simplicity. + * To do that, add '10' to the exponent. By doing that, we can just add + * 0x400 to normal values and get the expected result. + */ +static long pmbus_reg2data_ieee754(struct pmbus_data *data, + struct pmbus_sensor *sensor) +{ + int exponent; + bool sign; + long val; + + /* only support half precision for now */ + sign = sensor->data & 0x8000; + exponent = (sensor->data >> 10) & 0x1f; + val = sensor->data & 0x3ff; + + if (exponent == 0) { /* subnormal */ + exponent = -(14 + 10); + } else if (exponent == 0x1f) { /* NaN, convert to min/max */ + exponent = 0; + val = 65504; + } else { + exponent -= (15 + 10); /* normal */ + val |= 0x400; + } + + /* scale result to milli-units for all sensors except fans */ + if (sensor->class != PSC_FAN) + val = val * 1000L; + + /* scale result to micro-units for power sensors */ + if (sensor->class == PSC_POWER) + val = val * 1000L; + + if (exponent >= 0) + val <<= exponent; + else + val >>= -exponent; + + if (sign) + val = -val; + + return val; +} + +/* + * Convert linear sensor values to milli- or micro-units + * depending on sensor type. + */ +static s64 pmbus_reg2data_linear(struct pmbus_data *data, + struct pmbus_sensor *sensor) +{ + s16 exponent; + s32 mantissa; + s64 val; + + if (sensor->class == PSC_VOLTAGE_OUT) { /* LINEAR16 */ + exponent = data->exponent[sensor->page]; + mantissa = (u16) sensor->data; + } else { /* LINEAR11 */ + exponent = ((s16)sensor->data) >> 11; + mantissa = ((s16)((sensor->data & 0x7ff) << 5)) >> 5; + } + + val = mantissa; + + /* scale result to milli-units for all sensors except fans */ + if (sensor->class != PSC_FAN) + val = val * 1000LL; + + /* scale result to micro-units for power sensors */ + if (sensor->class == PSC_POWER) + val = val * 1000LL; + + if (exponent >= 0) + val <<= exponent; + else + val >>= -exponent; + + return val; +} + +/* + * Convert direct sensor values to milli- or micro-units + * depending on sensor type. + */ +static s64 pmbus_reg2data_direct(struct pmbus_data *data, + struct pmbus_sensor *sensor) +{ + s64 b, val = (s16)sensor->data; + s32 m, R; + + m = data->info->m[sensor->class]; + b = data->info->b[sensor->class]; + R = data->info->R[sensor->class]; + + if (m == 0) + return 0; + + /* X = 1/m * (Y * 10^-R - b) */ + R = -R; + /* scale result to milli-units for everything but fans */ + if (!(sensor->class == PSC_FAN || sensor->class == PSC_PWM)) { + R += 3; + b *= 1000; + } + + /* scale result to micro-units for power sensors */ + if (sensor->class == PSC_POWER) { + R += 3; + b *= 1000; + } + + while (R > 0) { + val *= 10; + R--; + } + while (R < 0) { + val = div_s64(val + 5LL, 10L); /* round closest */ + R++; + } + + val = div_s64(val - b, m); + return val; +} + +/* + * Convert VID sensor values to milli- or micro-units + * depending on sensor type. + */ +static s64 pmbus_reg2data_vid(struct pmbus_data *data, + struct pmbus_sensor *sensor) +{ + long val = sensor->data; + long rv = 0; + + switch (data->info->vrm_version[sensor->page]) { + case vr11: + if (val >= 0x02 && val <= 0xb2) + rv = DIV_ROUND_CLOSEST(160000 - (val - 2) * 625, 100); + break; + case vr12: + if (val >= 0x01) + rv = 250 + (val - 1) * 5; + break; + case vr13: + if (val >= 0x01) + rv = 500 + (val - 1) * 10; + break; + case imvp9: + if (val >= 0x01) + rv = 200 + (val - 1) * 10; + break; + case amd625mv: + if (val >= 0x0 && val <= 0xd8) + rv = DIV_ROUND_CLOSEST(155000 - val * 625, 100); + break; + } + return rv; +} + +static s64 pmbus_reg2data(struct pmbus_data *data, struct pmbus_sensor *sensor) +{ + s64 val; + + if (!sensor->convert) + return sensor->data; + + switch (data->info->format[sensor->class]) { + case direct: + val = pmbus_reg2data_direct(data, sensor); + break; + case vid: + val = pmbus_reg2data_vid(data, sensor); + break; + case ieee754: + val = pmbus_reg2data_ieee754(data, sensor); + break; + case linear: + default: + val = pmbus_reg2data_linear(data, sensor); + break; + } + return val; +} + +#define MAX_IEEE_MANTISSA (0x7ff * 1000) +#define MIN_IEEE_MANTISSA (0x400 * 1000) + +static u16 pmbus_data2reg_ieee754(struct pmbus_data *data, + struct pmbus_sensor *sensor, long val) +{ + u16 exponent = (15 + 10); + long mantissa; + u16 sign = 0; + + /* simple case */ + if (val == 0) + return 0; + + if (val < 0) { + sign = 0x8000; + val = -val; + } + + /* Power is in uW. Convert to mW before converting. */ + if (sensor->class == PSC_POWER) + val = DIV_ROUND_CLOSEST(val, 1000L); + + /* + * For simplicity, convert fan data to milli-units + * before calculating the exponent. + */ + if (sensor->class == PSC_FAN) + val = val * 1000; + + /* Reduce large mantissa until it fits into 10 bit */ + while (val > MAX_IEEE_MANTISSA && exponent < 30) { + exponent++; + val >>= 1; + } + /* + * Increase small mantissa to generate valid 'normal' + * number + */ + while (val < MIN_IEEE_MANTISSA && exponent > 1) { + exponent--; + val <<= 1; + } + + /* Convert mantissa from milli-units to units */ + mantissa = DIV_ROUND_CLOSEST(val, 1000); + + /* + * Ensure that the resulting number is within range. + * Valid range is 0x400..0x7ff, where bit 10 reflects + * the implied high bit in normalized ieee754 numbers. + * Set the range to 0x400..0x7ff to reflect this. + * The upper bit is then removed by the mask against + * 0x3ff in the final assignment. + */ + if (mantissa > 0x7ff) + mantissa = 0x7ff; + else if (mantissa < 0x400) + mantissa = 0x400; + + /* Convert to sign, 5 bit exponent, 10 bit mantissa */ + return sign | (mantissa & 0x3ff) | ((exponent << 10) & 0x7c00); +} + +#define MAX_LIN_MANTISSA (1023 * 1000) +#define MIN_LIN_MANTISSA (511 * 1000) + +static u16 pmbus_data2reg_linear(struct pmbus_data *data, + struct pmbus_sensor *sensor, s64 val) +{ + s16 exponent = 0, mantissa; + bool negative = false; + + /* simple case */ + if (val == 0) + return 0; + + if (sensor->class == PSC_VOLTAGE_OUT) { + /* LINEAR16 does not support negative voltages */ + if (val < 0) + return 0; + + /* + * For a static exponents, we don't have a choice + * but to adjust the value to it. + */ + if (data->exponent[sensor->page] < 0) + val <<= -data->exponent[sensor->page]; + else + val >>= data->exponent[sensor->page]; + val = DIV_ROUND_CLOSEST_ULL(val, 1000); + return clamp_val(val, 0, 0xffff); + } + + if (val < 0) { + negative = true; + val = -val; + } + + /* Power is in uW. Convert to mW before converting. */ + if (sensor->class == PSC_POWER) + val = DIV_ROUND_CLOSEST_ULL(val, 1000); + + /* + * For simplicity, convert fan data to milli-units + * before calculating the exponent. + */ + if (sensor->class == PSC_FAN) + val = val * 1000LL; + + /* Reduce large mantissa until it fits into 10 bit */ + while (val >= MAX_LIN_MANTISSA && exponent < 15) { + exponent++; + val >>= 1; + } + /* Increase small mantissa to improve precision */ + while (val < MIN_LIN_MANTISSA && exponent > -15) { + exponent--; + val <<= 1; + } + + /* Convert mantissa from milli-units to units */ + mantissa = clamp_val(DIV_ROUND_CLOSEST_ULL(val, 1000), 0, 0x3ff); + + /* restore sign */ + if (negative) + mantissa = -mantissa; + + /* Convert to 5 bit exponent, 11 bit mantissa */ + return (mantissa & 0x7ff) | ((exponent << 11) & 0xf800); +} + +static u16 pmbus_data2reg_direct(struct pmbus_data *data, + struct pmbus_sensor *sensor, s64 val) +{ + s64 b; + s32 m, R; + + m = data->info->m[sensor->class]; + b = data->info->b[sensor->class]; + R = data->info->R[sensor->class]; + + /* Power is in uW. Adjust R and b. */ + if (sensor->class == PSC_POWER) { + R -= 3; + b *= 1000; + } + + /* Calculate Y = (m * X + b) * 10^R */ + if (!(sensor->class == PSC_FAN || sensor->class == PSC_PWM)) { + R -= 3; /* Adjust R and b for data in milli-units */ + b *= 1000; + } + val = val * m + b; + + while (R > 0) { + val *= 10; + R--; + } + while (R < 0) { + val = div_s64(val + 5LL, 10L); /* round closest */ + R++; + } + + return (u16)clamp_val(val, S16_MIN, S16_MAX); +} + +static u16 pmbus_data2reg_vid(struct pmbus_data *data, + struct pmbus_sensor *sensor, s64 val) +{ + val = clamp_val(val, 500, 1600); + + return 2 + DIV_ROUND_CLOSEST_ULL((1600LL - val) * 100LL, 625); +} + +static u16 pmbus_data2reg(struct pmbus_data *data, + struct pmbus_sensor *sensor, s64 val) +{ + u16 regval; + + if (!sensor->convert) + return val; + + switch (data->info->format[sensor->class]) { + case direct: + regval = pmbus_data2reg_direct(data, sensor, val); + break; + case vid: + regval = pmbus_data2reg_vid(data, sensor, val); + break; + case ieee754: + regval = pmbus_data2reg_ieee754(data, sensor, val); + break; + case linear: + default: + regval = pmbus_data2reg_linear(data, sensor, val); + break; + } + return regval; +} + +/* + * Return boolean calculated from converted data. + * <index> defines a status register index and mask. + * The mask is in the lower 8 bits, the register index is in bits 8..23. + * + * The associated pmbus_boolean structure contains optional pointers to two + * sensor attributes. If specified, those attributes are compared against each + * other to determine if a limit has been exceeded. + * + * If the sensor attribute pointers are NULL, the function returns true if + * (status[reg] & mask) is true. + * + * If sensor attribute pointers are provided, a comparison against a specified + * limit has to be performed to determine the boolean result. + * In this case, the function returns true if v1 >= v2 (where v1 and v2 are + * sensor values referenced by sensor attribute pointers s1 and s2). + * + * To determine if an object exceeds upper limits, specify <s1,s2> = <v,limit>. + * To determine if an object exceeds lower limits, specify <s1,s2> = <limit,v>. + * + * If a negative value is stored in any of the referenced registers, this value + * reflects an error code which will be returned. + */ +static int pmbus_get_boolean(struct i2c_client *client, struct pmbus_boolean *b, + int index) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + struct pmbus_sensor *s1 = b->s1; + struct pmbus_sensor *s2 = b->s2; + u16 mask = pb_index_to_mask(index); + u8 page = pb_index_to_page(index); + u16 reg = pb_index_to_reg(index); + int ret, status; + u16 regval; + + mutex_lock(&data->update_lock); + status = pmbus_get_status(client, page, reg); + if (status < 0) { + ret = status; + goto unlock; + } + + if (s1) + pmbus_update_sensor_data(client, s1); + if (s2) + pmbus_update_sensor_data(client, s2); + + regval = status & mask; + if (regval) { + ret = _pmbus_write_byte_data(client, page, reg, regval); + if (ret) + goto unlock; + } + if (s1 && s2) { + s64 v1, v2; + + if (s1->data < 0) { + ret = s1->data; + goto unlock; + } + if (s2->data < 0) { + ret = s2->data; + goto unlock; + } + + v1 = pmbus_reg2data(data, s1); + v2 = pmbus_reg2data(data, s2); + ret = !!(regval && v1 >= v2); + } else { + ret = !!regval; + } +unlock: + mutex_unlock(&data->update_lock); + return ret; +} + +static ssize_t pmbus_show_boolean(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct pmbus_boolean *boolean = to_pmbus_boolean(attr); + struct i2c_client *client = to_i2c_client(dev->parent); + int val; + + val = pmbus_get_boolean(client, boolean, attr->index); + if (val < 0) + return val; + return sysfs_emit(buf, "%d\n", val); +} + +static ssize_t pmbus_show_sensor(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev->parent); + struct pmbus_sensor *sensor = to_pmbus_sensor(devattr); + struct pmbus_data *data = i2c_get_clientdata(client); + ssize_t ret; + + mutex_lock(&data->update_lock); + pmbus_update_sensor_data(client, sensor); + if (sensor->data < 0) + ret = sensor->data; + else + ret = sysfs_emit(buf, "%lld\n", pmbus_reg2data(data, sensor)); + mutex_unlock(&data->update_lock); + return ret; +} + +static ssize_t pmbus_set_sensor(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev->parent); + struct pmbus_data *data = i2c_get_clientdata(client); + struct pmbus_sensor *sensor = to_pmbus_sensor(devattr); + ssize_t rv = count; + s64 val; + int ret; + u16 regval; + + if (kstrtos64(buf, 10, &val) < 0) + return -EINVAL; + + mutex_lock(&data->update_lock); + regval = pmbus_data2reg(data, sensor, val); + ret = _pmbus_write_word_data(client, sensor->page, sensor->reg, regval); + if (ret < 0) + rv = ret; + else + sensor->data = -ENODATA; + mutex_unlock(&data->update_lock); + return rv; +} + +static ssize_t pmbus_show_label(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct pmbus_label *label = to_pmbus_label(da); + + return sysfs_emit(buf, "%s\n", label->label); +} + +static int pmbus_add_attribute(struct pmbus_data *data, struct attribute *attr) +{ + if (data->num_attributes >= data->max_attributes - 1) { + int new_max_attrs = data->max_attributes + PMBUS_ATTR_ALLOC_SIZE; + void *new_attrs = devm_krealloc(data->dev, data->group.attrs, + new_max_attrs * sizeof(void *), + GFP_KERNEL); + if (!new_attrs) + return -ENOMEM; + data->group.attrs = new_attrs; + data->max_attributes = new_max_attrs; + } + + data->group.attrs[data->num_attributes++] = attr; + data->group.attrs[data->num_attributes] = NULL; + return 0; +} + +static void pmbus_dev_attr_init(struct device_attribute *dev_attr, + const char *name, + umode_t mode, + ssize_t (*show)(struct device *dev, + struct device_attribute *attr, + char *buf), + ssize_t (*store)(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count)) +{ + sysfs_attr_init(&dev_attr->attr); + dev_attr->attr.name = name; + dev_attr->attr.mode = mode; + dev_attr->show = show; + dev_attr->store = store; +} + +static void pmbus_attr_init(struct sensor_device_attribute *a, + const char *name, + umode_t mode, + ssize_t (*show)(struct device *dev, + struct device_attribute *attr, + char *buf), + ssize_t (*store)(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count), + int idx) +{ + pmbus_dev_attr_init(&a->dev_attr, name, mode, show, store); + a->index = idx; +} + +static int pmbus_add_boolean(struct pmbus_data *data, + const char *name, const char *type, int seq, + struct pmbus_sensor *s1, + struct pmbus_sensor *s2, + u8 page, u16 reg, u16 mask) +{ + struct pmbus_boolean *boolean; + struct sensor_device_attribute *a; + + if (WARN((s1 && !s2) || (!s1 && s2), "Bad s1/s2 parameters\n")) + return -EINVAL; + + boolean = devm_kzalloc(data->dev, sizeof(*boolean), GFP_KERNEL); + if (!boolean) + return -ENOMEM; + + a = &boolean->attribute; + + snprintf(boolean->name, sizeof(boolean->name), "%s%d_%s", + name, seq, type); + boolean->s1 = s1; + boolean->s2 = s2; + pmbus_attr_init(a, boolean->name, 0444, pmbus_show_boolean, NULL, + pb_reg_to_index(page, reg, mask)); + + return pmbus_add_attribute(data, &a->dev_attr.attr); +} + +/* of thermal for pmbus temperature sensors */ +struct pmbus_thermal_data { + struct pmbus_data *pmbus_data; + struct pmbus_sensor *sensor; +}; + +static int pmbus_thermal_get_temp(struct thermal_zone_device *tz, int *temp) +{ + struct pmbus_thermal_data *tdata = tz->devdata; + struct pmbus_sensor *sensor = tdata->sensor; + struct pmbus_data *pmbus_data = tdata->pmbus_data; + struct i2c_client *client = to_i2c_client(pmbus_data->dev); + struct device *dev = pmbus_data->hwmon_dev; + int ret = 0; + + if (!dev) { + /* May not even get to hwmon yet */ + *temp = 0; + return 0; + } + + mutex_lock(&pmbus_data->update_lock); + pmbus_update_sensor_data(client, sensor); + if (sensor->data < 0) + ret = sensor->data; + else + *temp = (int)pmbus_reg2data(pmbus_data, sensor); + mutex_unlock(&pmbus_data->update_lock); + + return ret; +} + +static const struct thermal_zone_device_ops pmbus_thermal_ops = { + .get_temp = pmbus_thermal_get_temp, +}; + +static int pmbus_thermal_add_sensor(struct pmbus_data *pmbus_data, + struct pmbus_sensor *sensor, int index) +{ + struct device *dev = pmbus_data->dev; + struct pmbus_thermal_data *tdata; + struct thermal_zone_device *tzd; + + tdata = devm_kzalloc(dev, sizeof(*tdata), GFP_KERNEL); + if (!tdata) + return -ENOMEM; + + tdata->sensor = sensor; + tdata->pmbus_data = pmbus_data; + + tzd = devm_thermal_of_zone_register(dev, index, tdata, + &pmbus_thermal_ops); + /* + * If CONFIG_THERMAL_OF is disabled, this returns -ENODEV, + * so ignore that error but forward any other error. + */ + if (IS_ERR(tzd) && (PTR_ERR(tzd) != -ENODEV)) + return PTR_ERR(tzd); + + return 0; +} + +static struct pmbus_sensor *pmbus_add_sensor(struct pmbus_data *data, + const char *name, const char *type, + int seq, int page, int phase, + int reg, + enum pmbus_sensor_classes class, + bool update, bool readonly, + bool convert) +{ + struct pmbus_sensor *sensor; + struct device_attribute *a; + + sensor = devm_kzalloc(data->dev, sizeof(*sensor), GFP_KERNEL); + if (!sensor) + return NULL; + a = &sensor->attribute; + + if (type) + snprintf(sensor->name, sizeof(sensor->name), "%s%d_%s", + name, seq, type); + else + snprintf(sensor->name, sizeof(sensor->name), "%s%d", + name, seq); + + if (data->flags & PMBUS_WRITE_PROTECTED) + readonly = true; + + sensor->page = page; + sensor->phase = phase; + sensor->reg = reg; + sensor->class = class; + sensor->update = update; + sensor->convert = convert; + sensor->data = -ENODATA; + pmbus_dev_attr_init(a, sensor->name, + readonly ? 0444 : 0644, + pmbus_show_sensor, pmbus_set_sensor); + + if (pmbus_add_attribute(data, &a->attr)) + return NULL; + + sensor->next = data->sensors; + data->sensors = sensor; + + /* temperature sensors with _input values are registered with thermal */ + if (class == PSC_TEMPERATURE && strcmp(type, "input") == 0) + pmbus_thermal_add_sensor(data, sensor, seq); + + return sensor; +} + +static int pmbus_add_label(struct pmbus_data *data, + const char *name, int seq, + const char *lstring, int index, int phase) +{ + struct pmbus_label *label; + struct device_attribute *a; + + label = devm_kzalloc(data->dev, sizeof(*label), GFP_KERNEL); + if (!label) + return -ENOMEM; + + a = &label->attribute; + + snprintf(label->name, sizeof(label->name), "%s%d_label", name, seq); + if (!index) { + if (phase == 0xff) + strncpy(label->label, lstring, + sizeof(label->label) - 1); + else + snprintf(label->label, sizeof(label->label), "%s.%d", + lstring, phase); + } else { + if (phase == 0xff) + snprintf(label->label, sizeof(label->label), "%s%d", + lstring, index); + else + snprintf(label->label, sizeof(label->label), "%s%d.%d", + lstring, index, phase); + } + + pmbus_dev_attr_init(a, label->name, 0444, pmbus_show_label, NULL); + return pmbus_add_attribute(data, &a->attr); +} + +/* + * Search for attributes. Allocate sensors, booleans, and labels as needed. + */ + +/* + * The pmbus_limit_attr structure describes a single limit attribute + * and its associated alarm attribute. + */ +struct pmbus_limit_attr { + u16 reg; /* Limit register */ + u16 sbit; /* Alarm attribute status bit */ + bool update; /* True if register needs updates */ + bool low; /* True if low limit; for limits with compare + functions only */ + const char *attr; /* Attribute name */ + const char *alarm; /* Alarm attribute name */ +}; + +/* + * The pmbus_sensor_attr structure describes one sensor attribute. This + * description includes a reference to the associated limit attributes. + */ +struct pmbus_sensor_attr { + u16 reg; /* sensor register */ + u16 gbit; /* generic status bit */ + u8 nlimit; /* # of limit registers */ + enum pmbus_sensor_classes class;/* sensor class */ + const char *label; /* sensor label */ + bool paged; /* true if paged sensor */ + bool update; /* true if update needed */ + bool compare; /* true if compare function needed */ + u32 func; /* sensor mask */ + u32 sfunc; /* sensor status mask */ + int sreg; /* status register */ + const struct pmbus_limit_attr *limit;/* limit registers */ +}; + +/* + * Add a set of limit attributes and, if supported, the associated + * alarm attributes. + * returns 0 if no alarm register found, 1 if an alarm register was found, + * < 0 on errors. + */ +static int pmbus_add_limit_attrs(struct i2c_client *client, + struct pmbus_data *data, + const struct pmbus_driver_info *info, + const char *name, int index, int page, + struct pmbus_sensor *base, + const struct pmbus_sensor_attr *attr) +{ + const struct pmbus_limit_attr *l = attr->limit; + int nlimit = attr->nlimit; + int have_alarm = 0; + int i, ret; + struct pmbus_sensor *curr; + + for (i = 0; i < nlimit; i++) { + if (pmbus_check_word_register(client, page, l->reg)) { + curr = pmbus_add_sensor(data, name, l->attr, index, + page, 0xff, l->reg, attr->class, + attr->update || l->update, + false, true); + if (!curr) + return -ENOMEM; + if (l->sbit && (info->func[page] & attr->sfunc)) { + ret = pmbus_add_boolean(data, name, + l->alarm, index, + attr->compare ? l->low ? curr : base + : NULL, + attr->compare ? l->low ? base : curr + : NULL, + page, attr->sreg, l->sbit); + if (ret) + return ret; + have_alarm = 1; + } + } + l++; + } + return have_alarm; +} + +static int pmbus_add_sensor_attrs_one(struct i2c_client *client, + struct pmbus_data *data, + const struct pmbus_driver_info *info, + const char *name, + int index, int page, int phase, + const struct pmbus_sensor_attr *attr, + bool paged) +{ + struct pmbus_sensor *base; + bool upper = !!(attr->gbit & 0xff00); /* need to check STATUS_WORD */ + int ret; + + if (attr->label) { + ret = pmbus_add_label(data, name, index, attr->label, + paged ? page + 1 : 0, phase); + if (ret) + return ret; + } + base = pmbus_add_sensor(data, name, "input", index, page, phase, + attr->reg, attr->class, true, true, true); + if (!base) + return -ENOMEM; + /* No limit and alarm attributes for phase specific sensors */ + if (attr->sfunc && phase == 0xff) { + ret = pmbus_add_limit_attrs(client, data, info, name, + index, page, base, attr); + if (ret < 0) + return ret; + /* + * Add generic alarm attribute only if there are no individual + * alarm attributes, if there is a global alarm bit, and if + * the generic status register (word or byte, depending on + * which global bit is set) for this page is accessible. + */ + if (!ret && attr->gbit && + (!upper || data->has_status_word) && + pmbus_check_status_register(client, page)) { + ret = pmbus_add_boolean(data, name, "alarm", index, + NULL, NULL, + page, PMBUS_STATUS_WORD, + attr->gbit); + if (ret) + return ret; + } + } + return 0; +} + +static bool pmbus_sensor_is_paged(const struct pmbus_driver_info *info, + const struct pmbus_sensor_attr *attr) +{ + int p; + + if (attr->paged) + return true; + + /* + * Some attributes may be present on more than one page despite + * not being marked with the paged attribute. If that is the case, + * then treat the sensor as being paged and add the page suffix to the + * attribute name. + * We don't just add the paged attribute to all such attributes, in + * order to maintain the un-suffixed labels in the case where the + * attribute is only on page 0. + */ + for (p = 1; p < info->pages; p++) { + if (info->func[p] & attr->func) + return true; + } + return false; +} + +static int pmbus_add_sensor_attrs(struct i2c_client *client, + struct pmbus_data *data, + const char *name, + const struct pmbus_sensor_attr *attrs, + int nattrs) +{ + const struct pmbus_driver_info *info = data->info; + int index, i; + int ret; + + index = 1; + for (i = 0; i < nattrs; i++) { + int page, pages; + bool paged = pmbus_sensor_is_paged(info, attrs); + + pages = paged ? info->pages : 1; + for (page = 0; page < pages; page++) { + if (info->func[page] & attrs->func) { + ret = pmbus_add_sensor_attrs_one(client, data, info, + name, index, page, + 0xff, attrs, paged); + if (ret) + return ret; + index++; + } + if (info->phases[page]) { + int phase; + + for (phase = 0; phase < info->phases[page]; + phase++) { + if (!(info->pfunc[phase] & attrs->func)) + continue; + ret = pmbus_add_sensor_attrs_one(client, + data, info, name, index, page, + phase, attrs, paged); + if (ret) + return ret; + index++; + } + } + } + attrs++; + } + return 0; +} + +static const struct pmbus_limit_attr vin_limit_attrs[] = { + { + .reg = PMBUS_VIN_UV_WARN_LIMIT, + .attr = "min", + .alarm = "min_alarm", + .sbit = PB_VOLTAGE_UV_WARNING, + }, { + .reg = PMBUS_VIN_UV_FAULT_LIMIT, + .attr = "lcrit", + .alarm = "lcrit_alarm", + .sbit = PB_VOLTAGE_UV_FAULT | PB_VOLTAGE_VIN_OFF, + }, { + .reg = PMBUS_VIN_OV_WARN_LIMIT, + .attr = "max", + .alarm = "max_alarm", + .sbit = PB_VOLTAGE_OV_WARNING, + }, { + .reg = PMBUS_VIN_OV_FAULT_LIMIT, + .attr = "crit", + .alarm = "crit_alarm", + .sbit = PB_VOLTAGE_OV_FAULT, + }, { + .reg = PMBUS_VIRT_READ_VIN_AVG, + .update = true, + .attr = "average", + }, { + .reg = PMBUS_VIRT_READ_VIN_MIN, + .update = true, + .attr = "lowest", + }, { + .reg = PMBUS_VIRT_READ_VIN_MAX, + .update = true, + .attr = "highest", + }, { + .reg = PMBUS_VIRT_RESET_VIN_HISTORY, + .attr = "reset_history", + }, { + .reg = PMBUS_MFR_VIN_MIN, + .attr = "rated_min", + }, { + .reg = PMBUS_MFR_VIN_MAX, + .attr = "rated_max", + }, +}; + +static const struct pmbus_limit_attr vmon_limit_attrs[] = { + { + .reg = PMBUS_VIRT_VMON_UV_WARN_LIMIT, + .attr = "min", + .alarm = "min_alarm", + .sbit = PB_VOLTAGE_UV_WARNING, + }, { + .reg = PMBUS_VIRT_VMON_UV_FAULT_LIMIT, + .attr = "lcrit", + .alarm = "lcrit_alarm", + .sbit = PB_VOLTAGE_UV_FAULT, + }, { + .reg = PMBUS_VIRT_VMON_OV_WARN_LIMIT, + .attr = "max", + .alarm = "max_alarm", + .sbit = PB_VOLTAGE_OV_WARNING, + }, { + .reg = PMBUS_VIRT_VMON_OV_FAULT_LIMIT, + .attr = "crit", + .alarm = "crit_alarm", + .sbit = PB_VOLTAGE_OV_FAULT, + } +}; + +static const struct pmbus_limit_attr vout_limit_attrs[] = { + { + .reg = PMBUS_VOUT_UV_WARN_LIMIT, + .attr = "min", + .alarm = "min_alarm", + .sbit = PB_VOLTAGE_UV_WARNING, + }, { + .reg = PMBUS_VOUT_UV_FAULT_LIMIT, + .attr = "lcrit", + .alarm = "lcrit_alarm", + .sbit = PB_VOLTAGE_UV_FAULT, + }, { + .reg = PMBUS_VOUT_OV_WARN_LIMIT, + .attr = "max", + .alarm = "max_alarm", + .sbit = PB_VOLTAGE_OV_WARNING, + }, { + .reg = PMBUS_VOUT_OV_FAULT_LIMIT, + .attr = "crit", + .alarm = "crit_alarm", + .sbit = PB_VOLTAGE_OV_FAULT, + }, { + .reg = PMBUS_VIRT_READ_VOUT_AVG, + .update = true, + .attr = "average", + }, { + .reg = PMBUS_VIRT_READ_VOUT_MIN, + .update = true, + .attr = "lowest", + }, { + .reg = PMBUS_VIRT_READ_VOUT_MAX, + .update = true, + .attr = "highest", + }, { + .reg = PMBUS_VIRT_RESET_VOUT_HISTORY, + .attr = "reset_history", + }, { + .reg = PMBUS_MFR_VOUT_MIN, + .attr = "rated_min", + }, { + .reg = PMBUS_MFR_VOUT_MAX, + .attr = "rated_max", + }, +}; + +static const struct pmbus_sensor_attr voltage_attributes[] = { + { + .reg = PMBUS_READ_VIN, + .class = PSC_VOLTAGE_IN, + .label = "vin", + .func = PMBUS_HAVE_VIN, + .sfunc = PMBUS_HAVE_STATUS_INPUT, + .sreg = PMBUS_STATUS_INPUT, + .gbit = PB_STATUS_VIN_UV, + .limit = vin_limit_attrs, + .nlimit = ARRAY_SIZE(vin_limit_attrs), + }, { + .reg = PMBUS_VIRT_READ_VMON, + .class = PSC_VOLTAGE_IN, + .label = "vmon", + .func = PMBUS_HAVE_VMON, + .sfunc = PMBUS_HAVE_STATUS_VMON, + .sreg = PMBUS_VIRT_STATUS_VMON, + .limit = vmon_limit_attrs, + .nlimit = ARRAY_SIZE(vmon_limit_attrs), + }, { + .reg = PMBUS_READ_VCAP, + .class = PSC_VOLTAGE_IN, + .label = "vcap", + .func = PMBUS_HAVE_VCAP, + }, { + .reg = PMBUS_READ_VOUT, + .class = PSC_VOLTAGE_OUT, + .label = "vout", + .paged = true, + .func = PMBUS_HAVE_VOUT, + .sfunc = PMBUS_HAVE_STATUS_VOUT, + .sreg = PMBUS_STATUS_VOUT, + .gbit = PB_STATUS_VOUT_OV, + .limit = vout_limit_attrs, + .nlimit = ARRAY_SIZE(vout_limit_attrs), + } +}; + +/* Current attributes */ + +static const struct pmbus_limit_attr iin_limit_attrs[] = { + { + .reg = PMBUS_IIN_OC_WARN_LIMIT, + .attr = "max", + .alarm = "max_alarm", + .sbit = PB_IIN_OC_WARNING, + }, { + .reg = PMBUS_IIN_OC_FAULT_LIMIT, + .attr = "crit", + .alarm = "crit_alarm", + .sbit = PB_IIN_OC_FAULT, + }, { + .reg = PMBUS_VIRT_READ_IIN_AVG, + .update = true, + .attr = "average", + }, { + .reg = PMBUS_VIRT_READ_IIN_MIN, + .update = true, + .attr = "lowest", + }, { + .reg = PMBUS_VIRT_READ_IIN_MAX, + .update = true, + .attr = "highest", + }, { + .reg = PMBUS_VIRT_RESET_IIN_HISTORY, + .attr = "reset_history", + }, { + .reg = PMBUS_MFR_IIN_MAX, + .attr = "rated_max", + }, +}; + +static const struct pmbus_limit_attr iout_limit_attrs[] = { + { + .reg = PMBUS_IOUT_OC_WARN_LIMIT, + .attr = "max", + .alarm = "max_alarm", + .sbit = PB_IOUT_OC_WARNING, + }, { + .reg = PMBUS_IOUT_UC_FAULT_LIMIT, + .attr = "lcrit", + .alarm = "lcrit_alarm", + .sbit = PB_IOUT_UC_FAULT, + }, { + .reg = PMBUS_IOUT_OC_FAULT_LIMIT, + .attr = "crit", + .alarm = "crit_alarm", + .sbit = PB_IOUT_OC_FAULT, + }, { + .reg = PMBUS_VIRT_READ_IOUT_AVG, + .update = true, + .attr = "average", + }, { + .reg = PMBUS_VIRT_READ_IOUT_MIN, + .update = true, + .attr = "lowest", + }, { + .reg = PMBUS_VIRT_READ_IOUT_MAX, + .update = true, + .attr = "highest", + }, { + .reg = PMBUS_VIRT_RESET_IOUT_HISTORY, + .attr = "reset_history", + }, { + .reg = PMBUS_MFR_IOUT_MAX, + .attr = "rated_max", + }, +}; + +static const struct pmbus_sensor_attr current_attributes[] = { + { + .reg = PMBUS_READ_IIN, + .class = PSC_CURRENT_IN, + .label = "iin", + .func = PMBUS_HAVE_IIN, + .sfunc = PMBUS_HAVE_STATUS_INPUT, + .sreg = PMBUS_STATUS_INPUT, + .gbit = PB_STATUS_INPUT, + .limit = iin_limit_attrs, + .nlimit = ARRAY_SIZE(iin_limit_attrs), + }, { + .reg = PMBUS_READ_IOUT, + .class = PSC_CURRENT_OUT, + .label = "iout", + .paged = true, + .func = PMBUS_HAVE_IOUT, + .sfunc = PMBUS_HAVE_STATUS_IOUT, + .sreg = PMBUS_STATUS_IOUT, + .gbit = PB_STATUS_IOUT_OC, + .limit = iout_limit_attrs, + .nlimit = ARRAY_SIZE(iout_limit_attrs), + } +}; + +/* Power attributes */ + +static const struct pmbus_limit_attr pin_limit_attrs[] = { + { + .reg = PMBUS_PIN_OP_WARN_LIMIT, + .attr = "max", + .alarm = "alarm", + .sbit = PB_PIN_OP_WARNING, + }, { + .reg = PMBUS_VIRT_READ_PIN_AVG, + .update = true, + .attr = "average", + }, { + .reg = PMBUS_VIRT_READ_PIN_MIN, + .update = true, + .attr = "input_lowest", + }, { + .reg = PMBUS_VIRT_READ_PIN_MAX, + .update = true, + .attr = "input_highest", + }, { + .reg = PMBUS_VIRT_RESET_PIN_HISTORY, + .attr = "reset_history", + }, { + .reg = PMBUS_MFR_PIN_MAX, + .attr = "rated_max", + }, +}; + +static const struct pmbus_limit_attr pout_limit_attrs[] = { + { + .reg = PMBUS_POUT_MAX, + .attr = "cap", + .alarm = "cap_alarm", + .sbit = PB_POWER_LIMITING, + }, { + .reg = PMBUS_POUT_OP_WARN_LIMIT, + .attr = "max", + .alarm = "max_alarm", + .sbit = PB_POUT_OP_WARNING, + }, { + .reg = PMBUS_POUT_OP_FAULT_LIMIT, + .attr = "crit", + .alarm = "crit_alarm", + .sbit = PB_POUT_OP_FAULT, + }, { + .reg = PMBUS_VIRT_READ_POUT_AVG, + .update = true, + .attr = "average", + }, { + .reg = PMBUS_VIRT_READ_POUT_MIN, + .update = true, + .attr = "input_lowest", + }, { + .reg = PMBUS_VIRT_READ_POUT_MAX, + .update = true, + .attr = "input_highest", + }, { + .reg = PMBUS_VIRT_RESET_POUT_HISTORY, + .attr = "reset_history", + }, { + .reg = PMBUS_MFR_POUT_MAX, + .attr = "rated_max", + }, +}; + +static const struct pmbus_sensor_attr power_attributes[] = { + { + .reg = PMBUS_READ_PIN, + .class = PSC_POWER, + .label = "pin", + .func = PMBUS_HAVE_PIN, + .sfunc = PMBUS_HAVE_STATUS_INPUT, + .sreg = PMBUS_STATUS_INPUT, + .gbit = PB_STATUS_INPUT, + .limit = pin_limit_attrs, + .nlimit = ARRAY_SIZE(pin_limit_attrs), + }, { + .reg = PMBUS_READ_POUT, + .class = PSC_POWER, + .label = "pout", + .paged = true, + .func = PMBUS_HAVE_POUT, + .sfunc = PMBUS_HAVE_STATUS_IOUT, + .sreg = PMBUS_STATUS_IOUT, + .limit = pout_limit_attrs, + .nlimit = ARRAY_SIZE(pout_limit_attrs), + } +}; + +/* Temperature atributes */ + +static const struct pmbus_limit_attr temp_limit_attrs[] = { + { + .reg = PMBUS_UT_WARN_LIMIT, + .low = true, + .attr = "min", + .alarm = "min_alarm", + .sbit = PB_TEMP_UT_WARNING, + }, { + .reg = PMBUS_UT_FAULT_LIMIT, + .low = true, + .attr = "lcrit", + .alarm = "lcrit_alarm", + .sbit = PB_TEMP_UT_FAULT, + }, { + .reg = PMBUS_OT_WARN_LIMIT, + .attr = "max", + .alarm = "max_alarm", + .sbit = PB_TEMP_OT_WARNING, + }, { + .reg = PMBUS_OT_FAULT_LIMIT, + .attr = "crit", + .alarm = "crit_alarm", + .sbit = PB_TEMP_OT_FAULT, + }, { + .reg = PMBUS_VIRT_READ_TEMP_MIN, + .attr = "lowest", + }, { + .reg = PMBUS_VIRT_READ_TEMP_AVG, + .attr = "average", + }, { + .reg = PMBUS_VIRT_READ_TEMP_MAX, + .attr = "highest", + }, { + .reg = PMBUS_VIRT_RESET_TEMP_HISTORY, + .attr = "reset_history", + }, { + .reg = PMBUS_MFR_MAX_TEMP_1, + .attr = "rated_max", + }, +}; + +static const struct pmbus_limit_attr temp_limit_attrs2[] = { + { + .reg = PMBUS_UT_WARN_LIMIT, + .low = true, + .attr = "min", + .alarm = "min_alarm", + .sbit = PB_TEMP_UT_WARNING, + }, { + .reg = PMBUS_UT_FAULT_LIMIT, + .low = true, + .attr = "lcrit", + .alarm = "lcrit_alarm", + .sbit = PB_TEMP_UT_FAULT, + }, { + .reg = PMBUS_OT_WARN_LIMIT, + .attr = "max", + .alarm = "max_alarm", + .sbit = PB_TEMP_OT_WARNING, + }, { + .reg = PMBUS_OT_FAULT_LIMIT, + .attr = "crit", + .alarm = "crit_alarm", + .sbit = PB_TEMP_OT_FAULT, + }, { + .reg = PMBUS_VIRT_READ_TEMP2_MIN, + .attr = "lowest", + }, { + .reg = PMBUS_VIRT_READ_TEMP2_AVG, + .attr = "average", + }, { + .reg = PMBUS_VIRT_READ_TEMP2_MAX, + .attr = "highest", + }, { + .reg = PMBUS_VIRT_RESET_TEMP2_HISTORY, + .attr = "reset_history", + }, { + .reg = PMBUS_MFR_MAX_TEMP_2, + .attr = "rated_max", + }, +}; + +static const struct pmbus_limit_attr temp_limit_attrs3[] = { + { + .reg = PMBUS_UT_WARN_LIMIT, + .low = true, + .attr = "min", + .alarm = "min_alarm", + .sbit = PB_TEMP_UT_WARNING, + }, { + .reg = PMBUS_UT_FAULT_LIMIT, + .low = true, + .attr = "lcrit", + .alarm = "lcrit_alarm", + .sbit = PB_TEMP_UT_FAULT, + }, { + .reg = PMBUS_OT_WARN_LIMIT, + .attr = "max", + .alarm = "max_alarm", + .sbit = PB_TEMP_OT_WARNING, + }, { + .reg = PMBUS_OT_FAULT_LIMIT, + .attr = "crit", + .alarm = "crit_alarm", + .sbit = PB_TEMP_OT_FAULT, + }, { + .reg = PMBUS_MFR_MAX_TEMP_3, + .attr = "rated_max", + }, +}; + +static const struct pmbus_sensor_attr temp_attributes[] = { + { + .reg = PMBUS_READ_TEMPERATURE_1, + .class = PSC_TEMPERATURE, + .paged = true, + .update = true, + .compare = true, + .func = PMBUS_HAVE_TEMP, + .sfunc = PMBUS_HAVE_STATUS_TEMP, + .sreg = PMBUS_STATUS_TEMPERATURE, + .gbit = PB_STATUS_TEMPERATURE, + .limit = temp_limit_attrs, + .nlimit = ARRAY_SIZE(temp_limit_attrs), + }, { + .reg = PMBUS_READ_TEMPERATURE_2, + .class = PSC_TEMPERATURE, + .paged = true, + .update = true, + .compare = true, + .func = PMBUS_HAVE_TEMP2, + .sfunc = PMBUS_HAVE_STATUS_TEMP, + .sreg = PMBUS_STATUS_TEMPERATURE, + .gbit = PB_STATUS_TEMPERATURE, + .limit = temp_limit_attrs2, + .nlimit = ARRAY_SIZE(temp_limit_attrs2), + }, { + .reg = PMBUS_READ_TEMPERATURE_3, + .class = PSC_TEMPERATURE, + .paged = true, + .update = true, + .compare = true, + .func = PMBUS_HAVE_TEMP3, + .sfunc = PMBUS_HAVE_STATUS_TEMP, + .sreg = PMBUS_STATUS_TEMPERATURE, + .gbit = PB_STATUS_TEMPERATURE, + .limit = temp_limit_attrs3, + .nlimit = ARRAY_SIZE(temp_limit_attrs3), + } +}; + +static const int pmbus_fan_registers[] = { + PMBUS_READ_FAN_SPEED_1, + PMBUS_READ_FAN_SPEED_2, + PMBUS_READ_FAN_SPEED_3, + PMBUS_READ_FAN_SPEED_4 +}; + +static const int pmbus_fan_status_registers[] = { + PMBUS_STATUS_FAN_12, + PMBUS_STATUS_FAN_12, + PMBUS_STATUS_FAN_34, + PMBUS_STATUS_FAN_34 +}; + +static const u32 pmbus_fan_flags[] = { + PMBUS_HAVE_FAN12, + PMBUS_HAVE_FAN12, + PMBUS_HAVE_FAN34, + PMBUS_HAVE_FAN34 +}; + +static const u32 pmbus_fan_status_flags[] = { + PMBUS_HAVE_STATUS_FAN12, + PMBUS_HAVE_STATUS_FAN12, + PMBUS_HAVE_STATUS_FAN34, + PMBUS_HAVE_STATUS_FAN34 +}; + +/* Fans */ + +/* Precondition: FAN_CONFIG_x_y and FAN_COMMAND_x must exist for the fan ID */ +static int pmbus_add_fan_ctrl(struct i2c_client *client, + struct pmbus_data *data, int index, int page, int id, + u8 config) +{ + struct pmbus_sensor *sensor; + + sensor = pmbus_add_sensor(data, "fan", "target", index, page, + 0xff, PMBUS_VIRT_FAN_TARGET_1 + id, PSC_FAN, + false, false, true); + + if (!sensor) + return -ENOMEM; + + if (!((data->info->func[page] & PMBUS_HAVE_PWM12) || + (data->info->func[page] & PMBUS_HAVE_PWM34))) + return 0; + + sensor = pmbus_add_sensor(data, "pwm", NULL, index, page, + 0xff, PMBUS_VIRT_PWM_1 + id, PSC_PWM, + false, false, true); + + if (!sensor) + return -ENOMEM; + + sensor = pmbus_add_sensor(data, "pwm", "enable", index, page, + 0xff, PMBUS_VIRT_PWM_ENABLE_1 + id, PSC_PWM, + true, false, false); + + if (!sensor) + return -ENOMEM; + + return 0; +} + +static int pmbus_add_fan_attributes(struct i2c_client *client, + struct pmbus_data *data) +{ + const struct pmbus_driver_info *info = data->info; + int index = 1; + int page; + int ret; + + for (page = 0; page < info->pages; page++) { + int f; + + for (f = 0; f < ARRAY_SIZE(pmbus_fan_registers); f++) { + int regval; + + if (!(info->func[page] & pmbus_fan_flags[f])) + break; + + if (!pmbus_check_word_register(client, page, + pmbus_fan_registers[f])) + break; + + /* + * Skip fan if not installed. + * Each fan configuration register covers multiple fans, + * so we have to do some magic. + */ + regval = _pmbus_read_byte_data(client, page, + pmbus_fan_config_registers[f]); + if (regval < 0 || + (!(regval & (PB_FAN_1_INSTALLED >> ((f & 1) * 4))))) + continue; + + if (pmbus_add_sensor(data, "fan", "input", index, + page, 0xff, pmbus_fan_registers[f], + PSC_FAN, true, true, true) == NULL) + return -ENOMEM; + + /* Fan control */ + if (pmbus_check_word_register(client, page, + pmbus_fan_command_registers[f])) { + ret = pmbus_add_fan_ctrl(client, data, index, + page, f, regval); + if (ret < 0) + return ret; + } + + /* + * Each fan status register covers multiple fans, + * so we have to do some magic. + */ + if ((info->func[page] & pmbus_fan_status_flags[f]) && + pmbus_check_byte_register(client, + page, pmbus_fan_status_registers[f])) { + int reg; + + if (f > 1) /* fan 3, 4 */ + reg = PMBUS_STATUS_FAN_34; + else + reg = PMBUS_STATUS_FAN_12; + ret = pmbus_add_boolean(data, "fan", + "alarm", index, NULL, NULL, page, reg, + PB_FAN_FAN1_WARNING >> (f & 1)); + if (ret) + return ret; + ret = pmbus_add_boolean(data, "fan", + "fault", index, NULL, NULL, page, reg, + PB_FAN_FAN1_FAULT >> (f & 1)); + if (ret) + return ret; + } + index++; + } + } + return 0; +} + +struct pmbus_samples_attr { + int reg; + char *name; +}; + +struct pmbus_samples_reg { + int page; + struct pmbus_samples_attr *attr; + struct device_attribute dev_attr; +}; + +static struct pmbus_samples_attr pmbus_samples_registers[] = { + { + .reg = PMBUS_VIRT_SAMPLES, + .name = "samples", + }, { + .reg = PMBUS_VIRT_IN_SAMPLES, + .name = "in_samples", + }, { + .reg = PMBUS_VIRT_CURR_SAMPLES, + .name = "curr_samples", + }, { + .reg = PMBUS_VIRT_POWER_SAMPLES, + .name = "power_samples", + }, { + .reg = PMBUS_VIRT_TEMP_SAMPLES, + .name = "temp_samples", + } +}; + +#define to_samples_reg(x) container_of(x, struct pmbus_samples_reg, dev_attr) + +static ssize_t pmbus_show_samples(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int val; + struct i2c_client *client = to_i2c_client(dev->parent); + struct pmbus_samples_reg *reg = to_samples_reg(devattr); + struct pmbus_data *data = i2c_get_clientdata(client); + + mutex_lock(&data->update_lock); + val = _pmbus_read_word_data(client, reg->page, 0xff, reg->attr->reg); + mutex_unlock(&data->update_lock); + if (val < 0) + return val; + + return sysfs_emit(buf, "%d\n", val); +} + +static ssize_t pmbus_set_samples(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + int ret; + long val; + struct i2c_client *client = to_i2c_client(dev->parent); + struct pmbus_samples_reg *reg = to_samples_reg(devattr); + struct pmbus_data *data = i2c_get_clientdata(client); + + if (kstrtol(buf, 0, &val) < 0) + return -EINVAL; + + mutex_lock(&data->update_lock); + ret = _pmbus_write_word_data(client, reg->page, reg->attr->reg, val); + mutex_unlock(&data->update_lock); + + return ret ? : count; +} + +static int pmbus_add_samples_attr(struct pmbus_data *data, int page, + struct pmbus_samples_attr *attr) +{ + struct pmbus_samples_reg *reg; + + reg = devm_kzalloc(data->dev, sizeof(*reg), GFP_KERNEL); + if (!reg) + return -ENOMEM; + + reg->attr = attr; + reg->page = page; + + pmbus_dev_attr_init(®->dev_attr, attr->name, 0644, + pmbus_show_samples, pmbus_set_samples); + + return pmbus_add_attribute(data, ®->dev_attr.attr); +} + +static int pmbus_add_samples_attributes(struct i2c_client *client, + struct pmbus_data *data) +{ + const struct pmbus_driver_info *info = data->info; + int s; + + if (!(info->func[0] & PMBUS_HAVE_SAMPLES)) + return 0; + + for (s = 0; s < ARRAY_SIZE(pmbus_samples_registers); s++) { + struct pmbus_samples_attr *attr; + int ret; + + attr = &pmbus_samples_registers[s]; + if (!pmbus_check_word_register(client, 0, attr->reg)) + continue; + + ret = pmbus_add_samples_attr(data, 0, attr); + if (ret) + return ret; + } + + return 0; +} + +static int pmbus_find_attributes(struct i2c_client *client, + struct pmbus_data *data) +{ + int ret; + + /* Voltage sensors */ + ret = pmbus_add_sensor_attrs(client, data, "in", voltage_attributes, + ARRAY_SIZE(voltage_attributes)); + if (ret) + return ret; + + /* Current sensors */ + ret = pmbus_add_sensor_attrs(client, data, "curr", current_attributes, + ARRAY_SIZE(current_attributes)); + if (ret) + return ret; + + /* Power sensors */ + ret = pmbus_add_sensor_attrs(client, data, "power", power_attributes, + ARRAY_SIZE(power_attributes)); + if (ret) + return ret; + + /* Temperature sensors */ + ret = pmbus_add_sensor_attrs(client, data, "temp", temp_attributes, + ARRAY_SIZE(temp_attributes)); + if (ret) + return ret; + + /* Fans */ + ret = pmbus_add_fan_attributes(client, data); + if (ret) + return ret; + + ret = pmbus_add_samples_attributes(client, data); + return ret; +} + +/* + * The pmbus_class_attr_map structure maps one sensor class to + * it's corresponding sensor attributes array. + */ +struct pmbus_class_attr_map { + enum pmbus_sensor_classes class; + int nattr; + const struct pmbus_sensor_attr *attr; +}; + +static const struct pmbus_class_attr_map class_attr_map[] = { + { + .class = PSC_VOLTAGE_IN, + .attr = voltage_attributes, + .nattr = ARRAY_SIZE(voltage_attributes), + }, { + .class = PSC_VOLTAGE_OUT, + .attr = voltage_attributes, + .nattr = ARRAY_SIZE(voltage_attributes), + }, { + .class = PSC_CURRENT_IN, + .attr = current_attributes, + .nattr = ARRAY_SIZE(current_attributes), + }, { + .class = PSC_CURRENT_OUT, + .attr = current_attributes, + .nattr = ARRAY_SIZE(current_attributes), + }, { + .class = PSC_POWER, + .attr = power_attributes, + .nattr = ARRAY_SIZE(power_attributes), + }, { + .class = PSC_TEMPERATURE, + .attr = temp_attributes, + .nattr = ARRAY_SIZE(temp_attributes), + } +}; + +/* + * Read the coefficients for direct mode. + */ +static int pmbus_read_coefficients(struct i2c_client *client, + struct pmbus_driver_info *info, + const struct pmbus_sensor_attr *attr) +{ + int rv; + union i2c_smbus_data data; + enum pmbus_sensor_classes class = attr->class; + s8 R; + s16 m, b; + + data.block[0] = 2; + data.block[1] = attr->reg; + data.block[2] = 0x01; + + rv = i2c_smbus_xfer(client->adapter, client->addr, client->flags, + I2C_SMBUS_WRITE, PMBUS_COEFFICIENTS, + I2C_SMBUS_BLOCK_PROC_CALL, &data); + + if (rv < 0) + return rv; + + if (data.block[0] != 5) + return -EIO; + + m = data.block[1] | (data.block[2] << 8); + b = data.block[3] | (data.block[4] << 8); + R = data.block[5]; + info->m[class] = m; + info->b[class] = b; + info->R[class] = R; + + return rv; +} + +static int pmbus_init_coefficients(struct i2c_client *client, + struct pmbus_driver_info *info) +{ + int i, n, ret = -EINVAL; + const struct pmbus_class_attr_map *map; + const struct pmbus_sensor_attr *attr; + + for (i = 0; i < ARRAY_SIZE(class_attr_map); i++) { + map = &class_attr_map[i]; + if (info->format[map->class] != direct) + continue; + for (n = 0; n < map->nattr; n++) { + attr = &map->attr[n]; + if (map->class != attr->class) + continue; + ret = pmbus_read_coefficients(client, info, attr); + if (ret >= 0) + break; + } + if (ret < 0) { + dev_err(&client->dev, + "No coefficients found for sensor class %d\n", + map->class); + return -EINVAL; + } + } + + return 0; +} + +/* + * Identify chip parameters. + * This function is called for all chips. + */ +static int pmbus_identify_common(struct i2c_client *client, + struct pmbus_data *data, int page) +{ + int vout_mode = -1; + + if (pmbus_check_byte_register(client, page, PMBUS_VOUT_MODE)) + vout_mode = _pmbus_read_byte_data(client, page, + PMBUS_VOUT_MODE); + if (vout_mode >= 0 && vout_mode != 0xff) { + /* + * Not all chips support the VOUT_MODE command, + * so a failure to read it is not an error. + */ + switch (vout_mode >> 5) { + case 0: /* linear mode */ + if (data->info->format[PSC_VOLTAGE_OUT] != linear) + return -ENODEV; + + data->exponent[page] = ((s8)(vout_mode << 3)) >> 3; + break; + case 1: /* VID mode */ + if (data->info->format[PSC_VOLTAGE_OUT] != vid) + return -ENODEV; + break; + case 2: /* direct mode */ + if (data->info->format[PSC_VOLTAGE_OUT] != direct) + return -ENODEV; + break; + case 3: /* ieee 754 half precision */ + if (data->info->format[PSC_VOLTAGE_OUT] != ieee754) + return -ENODEV; + break; + default: + return -ENODEV; + } + } + + pmbus_clear_fault_page(client, page); + return 0; +} + +static int pmbus_read_status_byte(struct i2c_client *client, int page) +{ + return _pmbus_read_byte_data(client, page, PMBUS_STATUS_BYTE); +} + +static int pmbus_read_status_word(struct i2c_client *client, int page) +{ + return _pmbus_read_word_data(client, page, 0xff, PMBUS_STATUS_WORD); +} + +/* PEC attribute support */ + +static ssize_t pec_show(struct device *dev, struct device_attribute *dummy, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + + return sysfs_emit(buf, "%d\n", !!(client->flags & I2C_CLIENT_PEC)); +} + +static ssize_t pec_store(struct device *dev, struct device_attribute *dummy, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + bool enable; + int err; + + err = kstrtobool(buf, &enable); + if (err < 0) + return err; + + if (enable) + client->flags |= I2C_CLIENT_PEC; + else + client->flags &= ~I2C_CLIENT_PEC; + + return count; +} + +static DEVICE_ATTR_RW(pec); + +static void pmbus_remove_pec(void *dev) +{ + device_remove_file(dev, &dev_attr_pec); +} + +static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data, + struct pmbus_driver_info *info) +{ + struct device *dev = &client->dev; + int page, ret; + + /* + * Figure out if PEC is enabled before accessing any other register. + * Make sure PEC is disabled, will be enabled later if needed. + */ + client->flags &= ~I2C_CLIENT_PEC; + + /* Enable PEC if the controller and bus supports it */ + if (!(data->flags & PMBUS_NO_CAPABILITY)) { + ret = i2c_smbus_read_byte_data(client, PMBUS_CAPABILITY); + if (ret >= 0 && (ret & PB_CAPABILITY_ERROR_CHECK)) { + if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_PEC)) + client->flags |= I2C_CLIENT_PEC; + } + } + + /* + * Some PMBus chips don't support PMBUS_STATUS_WORD, so try + * to use PMBUS_STATUS_BYTE instead if that is the case. + * Bail out if both registers are not supported. + */ + data->read_status = pmbus_read_status_word; + ret = i2c_smbus_read_word_data(client, PMBUS_STATUS_WORD); + if (ret < 0 || ret == 0xffff) { + data->read_status = pmbus_read_status_byte; + ret = i2c_smbus_read_byte_data(client, PMBUS_STATUS_BYTE); + if (ret < 0 || ret == 0xff) { + dev_err(dev, "PMBus status register not found\n"); + return -ENODEV; + } + } else { + data->has_status_word = true; + } + + /* + * Check if the chip is write protected. If it is, we can not clear + * faults, and we should not try it. Also, in that case, writes into + * limit registers need to be disabled. + */ + if (!(data->flags & PMBUS_NO_WRITE_PROTECT)) { + ret = i2c_smbus_read_byte_data(client, PMBUS_WRITE_PROTECT); + if (ret > 0 && (ret & PB_WP_ANY)) + data->flags |= PMBUS_WRITE_PROTECTED | PMBUS_SKIP_STATUS_CHECK; + } + + if (data->info->pages) + pmbus_clear_faults(client); + else + pmbus_clear_fault_page(client, -1); + + if (info->identify) { + ret = (*info->identify)(client, info); + if (ret < 0) { + dev_err(dev, "Chip identification failed\n"); + return ret; + } + } + + if (info->pages <= 0 || info->pages > PMBUS_PAGES) { + dev_err(dev, "Bad number of PMBus pages: %d\n", info->pages); + return -ENODEV; + } + + for (page = 0; page < info->pages; page++) { + ret = pmbus_identify_common(client, data, page); + if (ret < 0) { + dev_err(dev, "Failed to identify chip capabilities\n"); + return ret; + } + } + + if (data->flags & PMBUS_USE_COEFFICIENTS_CMD) { + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BLOCK_PROC_CALL)) + return -ENODEV; + + ret = pmbus_init_coefficients(client, info); + if (ret < 0) + return ret; + } + + if (client->flags & I2C_CLIENT_PEC) { + /* + * If I2C_CLIENT_PEC is set here, both the I2C adapter and the + * chip support PEC. Add 'pec' attribute to client device to let + * the user control it. + */ + ret = device_create_file(dev, &dev_attr_pec); + if (ret) + return ret; + ret = devm_add_action_or_reset(dev, pmbus_remove_pec, dev); + if (ret) + return ret; + } + + return 0; +} + +#if IS_ENABLED(CONFIG_REGULATOR) +static int pmbus_regulator_is_enabled(struct regulator_dev *rdev) +{ + struct device *dev = rdev_get_dev(rdev); + struct i2c_client *client = to_i2c_client(dev->parent); + struct pmbus_data *data = i2c_get_clientdata(client); + u8 page = rdev_get_id(rdev); + int ret; + + mutex_lock(&data->update_lock); + ret = _pmbus_read_byte_data(client, page, PMBUS_OPERATION); + mutex_unlock(&data->update_lock); + + if (ret < 0) + return ret; + + return !!(ret & PB_OPERATION_CONTROL_ON); +} + +static int _pmbus_regulator_on_off(struct regulator_dev *rdev, bool enable) +{ + struct device *dev = rdev_get_dev(rdev); + struct i2c_client *client = to_i2c_client(dev->parent); + struct pmbus_data *data = i2c_get_clientdata(client); + u8 page = rdev_get_id(rdev); + int ret; + + mutex_lock(&data->update_lock); + ret = pmbus_update_byte_data(client, page, PMBUS_OPERATION, + PB_OPERATION_CONTROL_ON, + enable ? PB_OPERATION_CONTROL_ON : 0); + mutex_unlock(&data->update_lock); + + return ret; +} + +static int pmbus_regulator_enable(struct regulator_dev *rdev) +{ + return _pmbus_regulator_on_off(rdev, 1); +} + +static int pmbus_regulator_disable(struct regulator_dev *rdev) +{ + return _pmbus_regulator_on_off(rdev, 0); +} + +/* A PMBus status flag and the corresponding REGULATOR_ERROR_* flag */ +struct pmbus_regulator_status_assoc { + int pflag, rflag; +}; + +/* PMBus->regulator bit mappings for a PMBus status register */ +struct pmbus_regulator_status_category { + int func; + int reg; + const struct pmbus_regulator_status_assoc *bits; /* zero-terminated */ +}; + +static const struct pmbus_regulator_status_category pmbus_regulator_flag_map[] = { + { + .func = PMBUS_HAVE_STATUS_VOUT, + .reg = PMBUS_STATUS_VOUT, + .bits = (const struct pmbus_regulator_status_assoc[]) { + { PB_VOLTAGE_UV_WARNING, REGULATOR_ERROR_UNDER_VOLTAGE_WARN }, + { PB_VOLTAGE_UV_FAULT, REGULATOR_ERROR_UNDER_VOLTAGE }, + { PB_VOLTAGE_OV_WARNING, REGULATOR_ERROR_OVER_VOLTAGE_WARN }, + { PB_VOLTAGE_OV_FAULT, REGULATOR_ERROR_REGULATION_OUT }, + { }, + }, + }, { + .func = PMBUS_HAVE_STATUS_IOUT, + .reg = PMBUS_STATUS_IOUT, + .bits = (const struct pmbus_regulator_status_assoc[]) { + { PB_IOUT_OC_WARNING, REGULATOR_ERROR_OVER_CURRENT_WARN }, + { PB_IOUT_OC_FAULT, REGULATOR_ERROR_OVER_CURRENT }, + { PB_IOUT_OC_LV_FAULT, REGULATOR_ERROR_OVER_CURRENT }, + { }, + }, + }, { + .func = PMBUS_HAVE_STATUS_TEMP, + .reg = PMBUS_STATUS_TEMPERATURE, + .bits = (const struct pmbus_regulator_status_assoc[]) { + { PB_TEMP_OT_WARNING, REGULATOR_ERROR_OVER_TEMP_WARN }, + { PB_TEMP_OT_FAULT, REGULATOR_ERROR_OVER_TEMP }, + { }, + }, + }, +}; + +static int pmbus_regulator_get_error_flags(struct regulator_dev *rdev, unsigned int *flags) +{ + int i, status; + const struct pmbus_regulator_status_category *cat; + const struct pmbus_regulator_status_assoc *bit; + struct device *dev = rdev_get_dev(rdev); + struct i2c_client *client = to_i2c_client(dev->parent); + struct pmbus_data *data = i2c_get_clientdata(client); + u8 page = rdev_get_id(rdev); + int func = data->info->func[page]; + + *flags = 0; + + mutex_lock(&data->update_lock); + + for (i = 0; i < ARRAY_SIZE(pmbus_regulator_flag_map); i++) { + cat = &pmbus_regulator_flag_map[i]; + if (!(func & cat->func)) + continue; + + status = _pmbus_read_byte_data(client, page, cat->reg); + if (status < 0) { + mutex_unlock(&data->update_lock); + return status; + } + + for (bit = cat->bits; bit->pflag; bit++) { + if (status & bit->pflag) + *flags |= bit->rflag; + } + } + + /* + * Map what bits of STATUS_{WORD,BYTE} we can to REGULATOR_ERROR_* + * bits. Some of the other bits are tempting (especially for cases + * where we don't have the relevant PMBUS_HAVE_STATUS_* + * functionality), but there's an unfortunate ambiguity in that + * they're defined as indicating a fault *or* a warning, so we can't + * easily determine whether to report REGULATOR_ERROR_<foo> or + * REGULATOR_ERROR_<foo>_WARN. + */ + status = pmbus_get_status(client, page, PMBUS_STATUS_WORD); + mutex_unlock(&data->update_lock); + if (status < 0) + return status; + + if (pmbus_regulator_is_enabled(rdev) && (status & PB_STATUS_OFF)) + *flags |= REGULATOR_ERROR_FAIL; + + /* + * Unlike most other status bits, PB_STATUS_{IOUT_OC,VOUT_OV} are + * defined strictly as fault indicators (not warnings). + */ + if (status & PB_STATUS_IOUT_OC) + *flags |= REGULATOR_ERROR_OVER_CURRENT; + if (status & PB_STATUS_VOUT_OV) + *flags |= REGULATOR_ERROR_REGULATION_OUT; + + /* + * If we haven't discovered any thermal faults or warnings via + * PMBUS_STATUS_TEMPERATURE, map PB_STATUS_TEMPERATURE to a warning as + * a (conservative) best-effort interpretation. + */ + if (!(*flags & (REGULATOR_ERROR_OVER_TEMP | REGULATOR_ERROR_OVER_TEMP_WARN)) && + (status & PB_STATUS_TEMPERATURE)) + *flags |= REGULATOR_ERROR_OVER_TEMP_WARN; + + return 0; +} + +static int pmbus_regulator_get_low_margin(struct i2c_client *client, int page) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + struct pmbus_sensor s = { + .page = page, + .class = PSC_VOLTAGE_OUT, + .convert = true, + .data = -1, + }; + + if (data->vout_low[page] < 0) { + if (pmbus_check_word_register(client, page, PMBUS_MFR_VOUT_MIN)) + s.data = _pmbus_read_word_data(client, page, 0xff, + PMBUS_MFR_VOUT_MIN); + if (s.data < 0) { + s.data = _pmbus_read_word_data(client, page, 0xff, + PMBUS_VOUT_MARGIN_LOW); + if (s.data < 0) + return s.data; + } + data->vout_low[page] = pmbus_reg2data(data, &s); + } + + return data->vout_low[page]; +} + +static int pmbus_regulator_get_high_margin(struct i2c_client *client, int page) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + struct pmbus_sensor s = { + .page = page, + .class = PSC_VOLTAGE_OUT, + .convert = true, + .data = -1, + }; + + if (data->vout_high[page] < 0) { + if (pmbus_check_word_register(client, page, PMBUS_MFR_VOUT_MAX)) + s.data = _pmbus_read_word_data(client, page, 0xff, + PMBUS_MFR_VOUT_MAX); + if (s.data < 0) { + s.data = _pmbus_read_word_data(client, page, 0xff, + PMBUS_VOUT_MARGIN_HIGH); + if (s.data < 0) + return s.data; + } + data->vout_high[page] = pmbus_reg2data(data, &s); + } + + return data->vout_high[page]; +} + +static int pmbus_regulator_get_voltage(struct regulator_dev *rdev) +{ + struct device *dev = rdev_get_dev(rdev); + struct i2c_client *client = to_i2c_client(dev->parent); + struct pmbus_data *data = i2c_get_clientdata(client); + struct pmbus_sensor s = { + .page = rdev_get_id(rdev), + .class = PSC_VOLTAGE_OUT, + .convert = true, + }; + + s.data = _pmbus_read_word_data(client, s.page, 0xff, PMBUS_READ_VOUT); + if (s.data < 0) + return s.data; + + return (int)pmbus_reg2data(data, &s) * 1000; /* unit is uV */ +} + +static int pmbus_regulator_set_voltage(struct regulator_dev *rdev, int min_uv, + int max_uv, unsigned int *selector) +{ + struct device *dev = rdev_get_dev(rdev); + struct i2c_client *client = to_i2c_client(dev->parent); + struct pmbus_data *data = i2c_get_clientdata(client); + struct pmbus_sensor s = { + .page = rdev_get_id(rdev), + .class = PSC_VOLTAGE_OUT, + .convert = true, + .data = -1, + }; + int val = DIV_ROUND_CLOSEST(min_uv, 1000); /* convert to mV */ + int low, high; + + *selector = 0; + + low = pmbus_regulator_get_low_margin(client, s.page); + if (low < 0) + return low; + + high = pmbus_regulator_get_high_margin(client, s.page); + if (high < 0) + return high; + + /* Make sure we are within margins */ + if (low > val) + val = low; + if (high < val) + val = high; + + val = pmbus_data2reg(data, &s, val); + + return _pmbus_write_word_data(client, s.page, PMBUS_VOUT_COMMAND, (u16)val); +} + +static int pmbus_regulator_list_voltage(struct regulator_dev *rdev, + unsigned int selector) +{ + struct device *dev = rdev_get_dev(rdev); + struct i2c_client *client = to_i2c_client(dev->parent); + int val, low, high; + + if (selector >= rdev->desc->n_voltages || + selector < rdev->desc->linear_min_sel) + return -EINVAL; + + selector -= rdev->desc->linear_min_sel; + val = DIV_ROUND_CLOSEST(rdev->desc->min_uV + + (rdev->desc->uV_step * selector), 1000); /* convert to mV */ + + low = pmbus_regulator_get_low_margin(client, rdev_get_id(rdev)); + if (low < 0) + return low; + + high = pmbus_regulator_get_high_margin(client, rdev_get_id(rdev)); + if (high < 0) + return high; + + if (val >= low && val <= high) + return val * 1000; /* unit is uV */ + + return 0; +} + +const struct regulator_ops pmbus_regulator_ops = { + .enable = pmbus_regulator_enable, + .disable = pmbus_regulator_disable, + .is_enabled = pmbus_regulator_is_enabled, + .get_error_flags = pmbus_regulator_get_error_flags, + .get_voltage = pmbus_regulator_get_voltage, + .set_voltage = pmbus_regulator_set_voltage, + .list_voltage = pmbus_regulator_list_voltage, +}; +EXPORT_SYMBOL_NS_GPL(pmbus_regulator_ops, PMBUS); + +static int pmbus_regulator_register(struct pmbus_data *data) +{ + struct device *dev = data->dev; + const struct pmbus_driver_info *info = data->info; + const struct pmbus_platform_data *pdata = dev_get_platdata(dev); + struct regulator_dev *rdev; + int i; + + for (i = 0; i < info->num_regulators; i++) { + struct regulator_config config = { }; + + config.dev = dev; + config.driver_data = data; + + if (pdata && pdata->reg_init_data) + config.init_data = &pdata->reg_init_data[i]; + + rdev = devm_regulator_register(dev, &info->reg_desc[i], + &config); + if (IS_ERR(rdev)) + return dev_err_probe(dev, PTR_ERR(rdev), + "Failed to register %s regulator\n", + info->reg_desc[i].name); + } + + return 0; +} +#else +static int pmbus_regulator_register(struct pmbus_data *data) +{ + return 0; +} +#endif + +static struct dentry *pmbus_debugfs_dir; /* pmbus debugfs directory */ + +#if IS_ENABLED(CONFIG_DEBUG_FS) +static int pmbus_debugfs_get(void *data, u64 *val) +{ + int rc; + struct pmbus_debugfs_entry *entry = data; + + rc = _pmbus_read_byte_data(entry->client, entry->page, entry->reg); + if (rc < 0) + return rc; + + *val = rc; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(pmbus_debugfs_ops, pmbus_debugfs_get, NULL, + "0x%02llx\n"); + +static int pmbus_debugfs_get_status(void *data, u64 *val) +{ + int rc; + struct pmbus_debugfs_entry *entry = data; + struct pmbus_data *pdata = i2c_get_clientdata(entry->client); + + rc = pdata->read_status(entry->client, entry->page); + if (rc < 0) + return rc; + + *val = rc; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(pmbus_debugfs_ops_status, pmbus_debugfs_get_status, + NULL, "0x%04llx\n"); + +static ssize_t pmbus_debugfs_mfr_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + int rc; + struct pmbus_debugfs_entry *entry = file->private_data; + char data[I2C_SMBUS_BLOCK_MAX + 2] = { 0 }; + + rc = pmbus_read_block_data(entry->client, entry->page, entry->reg, + data); + if (rc < 0) + return rc; + + /* Add newline at the end of a read data */ + data[rc] = '\n'; + + /* Include newline into the length */ + rc += 1; + + return simple_read_from_buffer(buf, count, ppos, data, rc); +} + +static const struct file_operations pmbus_debugfs_ops_mfr = { + .llseek = noop_llseek, + .read = pmbus_debugfs_mfr_read, + .write = NULL, + .open = simple_open, +}; + +static void pmbus_remove_debugfs(void *data) +{ + struct dentry *entry = data; + + debugfs_remove_recursive(entry); +} + +static int pmbus_init_debugfs(struct i2c_client *client, + struct pmbus_data *data) +{ + int i, idx = 0; + char name[PMBUS_NAME_SIZE]; + struct pmbus_debugfs_entry *entries; + + if (!pmbus_debugfs_dir) + return -ENODEV; + + /* + * Create the debugfs directory for this device. Use the hwmon device + * name to avoid conflicts (hwmon numbers are globally unique). + */ + data->debugfs = debugfs_create_dir(dev_name(data->hwmon_dev), + pmbus_debugfs_dir); + if (IS_ERR_OR_NULL(data->debugfs)) { + data->debugfs = NULL; + return -ENODEV; + } + + /* + * Allocate the max possible entries we need. + * 6 entries device-specific + * 10 entries page-specific + */ + entries = devm_kcalloc(data->dev, + 6 + data->info->pages * 10, sizeof(*entries), + GFP_KERNEL); + if (!entries) + return -ENOMEM; + + /* + * Add device-specific entries. + * Please note that the PMBUS standard allows all registers to be + * page-specific. + * To reduce the number of debugfs entries for devices with many pages + * assume that values of the following registers are the same for all + * pages and report values only for page 0. + */ + if (pmbus_check_block_register(client, 0, PMBUS_MFR_ID)) { + entries[idx].client = client; + entries[idx].page = 0; + entries[idx].reg = PMBUS_MFR_ID; + debugfs_create_file("mfr_id", 0444, data->debugfs, + &entries[idx++], + &pmbus_debugfs_ops_mfr); + } + + if (pmbus_check_block_register(client, 0, PMBUS_MFR_MODEL)) { + entries[idx].client = client; + entries[idx].page = 0; + entries[idx].reg = PMBUS_MFR_MODEL; + debugfs_create_file("mfr_model", 0444, data->debugfs, + &entries[idx++], + &pmbus_debugfs_ops_mfr); + } + + if (pmbus_check_block_register(client, 0, PMBUS_MFR_REVISION)) { + entries[idx].client = client; + entries[idx].page = 0; + entries[idx].reg = PMBUS_MFR_REVISION; + debugfs_create_file("mfr_revision", 0444, data->debugfs, + &entries[idx++], + &pmbus_debugfs_ops_mfr); + } + + if (pmbus_check_block_register(client, 0, PMBUS_MFR_LOCATION)) { + entries[idx].client = client; + entries[idx].page = 0; + entries[idx].reg = PMBUS_MFR_LOCATION; + debugfs_create_file("mfr_location", 0444, data->debugfs, + &entries[idx++], + &pmbus_debugfs_ops_mfr); + } + + if (pmbus_check_block_register(client, 0, PMBUS_MFR_DATE)) { + entries[idx].client = client; + entries[idx].page = 0; + entries[idx].reg = PMBUS_MFR_DATE; + debugfs_create_file("mfr_date", 0444, data->debugfs, + &entries[idx++], + &pmbus_debugfs_ops_mfr); + } + + if (pmbus_check_block_register(client, 0, PMBUS_MFR_SERIAL)) { + entries[idx].client = client; + entries[idx].page = 0; + entries[idx].reg = PMBUS_MFR_SERIAL; + debugfs_create_file("mfr_serial", 0444, data->debugfs, + &entries[idx++], + &pmbus_debugfs_ops_mfr); + } + + /* Add page specific entries */ + for (i = 0; i < data->info->pages; ++i) { + /* Check accessibility of status register if it's not page 0 */ + if (!i || pmbus_check_status_register(client, i)) { + /* No need to set reg as we have special read op. */ + entries[idx].client = client; + entries[idx].page = i; + scnprintf(name, PMBUS_NAME_SIZE, "status%d", i); + debugfs_create_file(name, 0444, data->debugfs, + &entries[idx++], + &pmbus_debugfs_ops_status); + } + + if (data->info->func[i] & PMBUS_HAVE_STATUS_VOUT) { + entries[idx].client = client; + entries[idx].page = i; + entries[idx].reg = PMBUS_STATUS_VOUT; + scnprintf(name, PMBUS_NAME_SIZE, "status%d_vout", i); + debugfs_create_file(name, 0444, data->debugfs, + &entries[idx++], + &pmbus_debugfs_ops); + } + + if (data->info->func[i] & PMBUS_HAVE_STATUS_IOUT) { + entries[idx].client = client; + entries[idx].page = i; + entries[idx].reg = PMBUS_STATUS_IOUT; + scnprintf(name, PMBUS_NAME_SIZE, "status%d_iout", i); + debugfs_create_file(name, 0444, data->debugfs, + &entries[idx++], + &pmbus_debugfs_ops); + } + + if (data->info->func[i] & PMBUS_HAVE_STATUS_INPUT) { + entries[idx].client = client; + entries[idx].page = i; + entries[idx].reg = PMBUS_STATUS_INPUT; + scnprintf(name, PMBUS_NAME_SIZE, "status%d_input", i); + debugfs_create_file(name, 0444, data->debugfs, + &entries[idx++], + &pmbus_debugfs_ops); + } + + if (data->info->func[i] & PMBUS_HAVE_STATUS_TEMP) { + entries[idx].client = client; + entries[idx].page = i; + entries[idx].reg = PMBUS_STATUS_TEMPERATURE; + scnprintf(name, PMBUS_NAME_SIZE, "status%d_temp", i); + debugfs_create_file(name, 0444, data->debugfs, + &entries[idx++], + &pmbus_debugfs_ops); + } + + if (pmbus_check_byte_register(client, i, PMBUS_STATUS_CML)) { + entries[idx].client = client; + entries[idx].page = i; + entries[idx].reg = PMBUS_STATUS_CML; + scnprintf(name, PMBUS_NAME_SIZE, "status%d_cml", i); + debugfs_create_file(name, 0444, data->debugfs, + &entries[idx++], + &pmbus_debugfs_ops); + } + + if (pmbus_check_byte_register(client, i, PMBUS_STATUS_OTHER)) { + entries[idx].client = client; + entries[idx].page = i; + entries[idx].reg = PMBUS_STATUS_OTHER; + scnprintf(name, PMBUS_NAME_SIZE, "status%d_other", i); + debugfs_create_file(name, 0444, data->debugfs, + &entries[idx++], + &pmbus_debugfs_ops); + } + + if (pmbus_check_byte_register(client, i, + PMBUS_STATUS_MFR_SPECIFIC)) { + entries[idx].client = client; + entries[idx].page = i; + entries[idx].reg = PMBUS_STATUS_MFR_SPECIFIC; + scnprintf(name, PMBUS_NAME_SIZE, "status%d_mfr", i); + debugfs_create_file(name, 0444, data->debugfs, + &entries[idx++], + &pmbus_debugfs_ops); + } + + if (data->info->func[i] & PMBUS_HAVE_STATUS_FAN12) { + entries[idx].client = client; + entries[idx].page = i; + entries[idx].reg = PMBUS_STATUS_FAN_12; + scnprintf(name, PMBUS_NAME_SIZE, "status%d_fan12", i); + debugfs_create_file(name, 0444, data->debugfs, + &entries[idx++], + &pmbus_debugfs_ops); + } + + if (data->info->func[i] & PMBUS_HAVE_STATUS_FAN34) { + entries[idx].client = client; + entries[idx].page = i; + entries[idx].reg = PMBUS_STATUS_FAN_34; + scnprintf(name, PMBUS_NAME_SIZE, "status%d_fan34", i); + debugfs_create_file(name, 0444, data->debugfs, + &entries[idx++], + &pmbus_debugfs_ops); + } + } + + return devm_add_action_or_reset(data->dev, + pmbus_remove_debugfs, data->debugfs); +} +#else +static int pmbus_init_debugfs(struct i2c_client *client, + struct pmbus_data *data) +{ + return 0; +} +#endif /* IS_ENABLED(CONFIG_DEBUG_FS) */ + +int pmbus_do_probe(struct i2c_client *client, struct pmbus_driver_info *info) +{ + struct device *dev = &client->dev; + const struct pmbus_platform_data *pdata = dev_get_platdata(dev); + struct pmbus_data *data; + size_t groups_num = 0; + int ret; + int i; + char *name; + + if (!info) + return -ENODEV; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WRITE_BYTE + | I2C_FUNC_SMBUS_BYTE_DATA + | I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + if (info->groups) + while (info->groups[groups_num]) + groups_num++; + + data->groups = devm_kcalloc(dev, groups_num + 2, sizeof(void *), + GFP_KERNEL); + if (!data->groups) + return -ENOMEM; + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + data->dev = dev; + + if (pdata) + data->flags = pdata->flags; + data->info = info; + data->currpage = -1; + data->currphase = -1; + + for (i = 0; i < ARRAY_SIZE(data->vout_low); i++) { + data->vout_low[i] = -1; + data->vout_high[i] = -1; + } + + ret = pmbus_init_common(client, data, info); + if (ret < 0) + return ret; + + ret = pmbus_find_attributes(client, data); + if (ret) + return ret; + + /* + * If there are no attributes, something is wrong. + * Bail out instead of trying to register nothing. + */ + if (!data->num_attributes) { + dev_err(dev, "No attributes found\n"); + 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, + 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); + } + + ret = pmbus_regulator_register(data); + if (ret) + return ret; + + ret = pmbus_init_debugfs(client, data); + if (ret) + dev_warn(dev, "Failed to register debugfs\n"); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(pmbus_do_probe, PMBUS); + +struct dentry *pmbus_get_debugfs_dir(struct i2c_client *client) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + + return data->debugfs; +} +EXPORT_SYMBOL_NS_GPL(pmbus_get_debugfs_dir, PMBUS); + +static int __init pmbus_core_init(void) +{ + pmbus_debugfs_dir = debugfs_create_dir("pmbus", NULL); + if (IS_ERR(pmbus_debugfs_dir)) + pmbus_debugfs_dir = NULL; + + return 0; +} + +static void __exit pmbus_core_exit(void) +{ + debugfs_remove_recursive(pmbus_debugfs_dir); +} + +module_init(pmbus_core_init); +module_exit(pmbus_core_exit); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus core driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/pxe1610.c b/drivers/hwmon/pmbus/pxe1610.c new file mode 100644 index 000000000..52bee5de2 --- /dev/null +++ b/drivers/hwmon/pmbus/pxe1610.c @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Hardware monitoring driver for Infineon PXE1610 + * + * Copyright (c) 2019 Facebook Inc + * + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include "pmbus.h" + +#define PXE1610_NUM_PAGES 3 + +/* Identify chip parameters. */ +static int pxe1610_identify(struct i2c_client *client, + struct pmbus_driver_info *info) +{ + int i; + + for (i = 0; i < PXE1610_NUM_PAGES; i++) { + if (pmbus_check_byte_register(client, i, PMBUS_VOUT_MODE)) { + u8 vout_mode; + int ret; + + /* Read the register with VOUT scaling value.*/ + ret = pmbus_read_byte_data(client, i, PMBUS_VOUT_MODE); + if (ret < 0) + return ret; + + vout_mode = ret & GENMASK(4, 0); + + switch (vout_mode) { + case 1: + info->vrm_version[i] = vr12; + break; + case 2: + 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; + } + } + } + + return 0; +} + +static struct pmbus_driver_info pxe1610_info = { + .pages = PXE1610_NUM_PAGES, + .format[PSC_VOLTAGE_IN] = linear, + .format[PSC_VOLTAGE_OUT] = vid, + .format[PSC_CURRENT_IN] = linear, + .format[PSC_CURRENT_OUT] = linear, + .format[PSC_TEMPERATURE] = linear, + .format[PSC_POWER] = 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_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT + | PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP, + .func[1] = PMBUS_HAVE_VIN + | PMBUS_HAVE_VOUT | PMBUS_HAVE_IIN + | PMBUS_HAVE_IOUT | PMBUS_HAVE_PIN + | PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP + | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT + | PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP, + .func[2] = PMBUS_HAVE_VIN + | PMBUS_HAVE_VOUT | PMBUS_HAVE_IIN + | PMBUS_HAVE_IOUT | PMBUS_HAVE_PIN + | PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP + | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT + | PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP, + .identify = pxe1610_identify, +}; + +static int pxe1610_probe(struct i2c_client *client) +{ + struct pmbus_driver_info *info; + 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; + + /* + * By default this device doesn't boot to page 0, so set page 0 + * to access all pmbus registers. + */ + i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); + + /* Read Manufacturer id */ + ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf); + if (ret < 0) { + dev_err(&client->dev, "Failed to read PMBUS_MFR_ID\n"); + return ret; + } + if (ret != 2 || strncmp(buf, "XP", 2)) { + dev_err(&client->dev, "MFR_ID unrecognized\n"); + return -ENODEV; + } + + info = devm_kmemdup(&client->dev, &pxe1610_info, + sizeof(struct pmbus_driver_info), + GFP_KERNEL); + if (!info) + return -ENOMEM; + + return pmbus_do_probe(client, info); +} + +static const struct i2c_device_id pxe1610_id[] = { + {"pxe1610", 0}, + {"pxe1110", 0}, + {"pxm1310", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, pxe1610_id); + +static struct i2c_driver pxe1610_driver = { + .driver = { + .name = "pxe1610", + }, + .probe_new = pxe1610_probe, + .id_table = pxe1610_id, +}; + +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 new file mode 100644 index 000000000..fa298b426 --- /dev/null +++ b/drivers/hwmon/pmbus/q54sj108a2.c @@ -0,0 +1,423 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Delta modules, Q54SJ108A2 series 1/4 Brick DC/DC + * Regulated Power Module + * + * Copyright 2020 Delta LLC. + */ + +#include <linux/debugfs.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include "pmbus.h" + +#define STORE_DEFAULT_ALL 0x11 +#define ERASE_BLACKBOX_DATA 0xD1 +#define READ_HISTORY_EVENT_NUMBER 0xD2 +#define READ_HISTORY_EVENTS 0xE0 +#define SET_HISTORY_EVENT_OFFSET 0xE1 +#define PMBUS_FLASH_KEY_WRITE 0xEC + +enum chips { + q54sj108a2 +}; + +enum { + Q54SJ108A2_DEBUGFS_OPERATION = 0, + Q54SJ108A2_DEBUGFS_CLEARFAULT, + Q54SJ108A2_DEBUGFS_WRITEPROTECT, + Q54SJ108A2_DEBUGFS_STOREDEFAULT, + Q54SJ108A2_DEBUGFS_VOOV_RESPONSE, + Q54SJ108A2_DEBUGFS_IOOC_RESPONSE, + Q54SJ108A2_DEBUGFS_PMBUS_VERSION, + Q54SJ108A2_DEBUGFS_MFR_ID, + Q54SJ108A2_DEBUGFS_MFR_MODEL, + Q54SJ108A2_DEBUGFS_MFR_REVISION, + Q54SJ108A2_DEBUGFS_MFR_LOCATION, + Q54SJ108A2_DEBUGFS_BLACKBOX_ERASE, + Q54SJ108A2_DEBUGFS_BLACKBOX_READ_OFFSET, + Q54SJ108A2_DEBUGFS_BLACKBOX_SET_OFFSET, + Q54SJ108A2_DEBUGFS_BLACKBOX_READ, + Q54SJ108A2_DEBUGFS_FLASH_KEY, + Q54SJ108A2_DEBUGFS_NUM_ENTRIES +}; + +struct q54sj108a2_data { + enum chips chip; + struct i2c_client *client; + + int debugfs_entries[Q54SJ108A2_DEBUGFS_NUM_ENTRIES]; +}; + +#define to_psu(x, y) container_of((x), struct q54sj108a2_data, debugfs_entries[(y)]) + +static struct pmbus_driver_info q54sj108a2_info[] = { + [q54sj108a2] = { + .pages = 1, + + /* Source : Delta Q54SJ108A2 */ + .format[PSC_TEMPERATURE] = linear, + .format[PSC_VOLTAGE_IN] = linear, + .format[PSC_CURRENT_OUT] = linear, + + .func[0] = PMBUS_HAVE_VIN | + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_STATUS_INPUT, + }, +}; + +static ssize_t q54sj108a2_debugfs_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + int rc; + int *idxp = file->private_data; + int idx = *idxp; + struct q54sj108a2_data *psu = to_psu(idxp, idx); + char data[I2C_SMBUS_BLOCK_MAX + 2] = { 0 }; + char data_char[I2C_SMBUS_BLOCK_MAX + 2] = { 0 }; + char *res; + + switch (idx) { + case Q54SJ108A2_DEBUGFS_OPERATION: + rc = i2c_smbus_read_byte_data(psu->client, PMBUS_OPERATION); + if (rc < 0) + return rc; + + rc = snprintf(data, 3, "%02x", rc); + break; + case Q54SJ108A2_DEBUGFS_WRITEPROTECT: + rc = i2c_smbus_read_byte_data(psu->client, PMBUS_WRITE_PROTECT); + if (rc < 0) + return rc; + + rc = snprintf(data, 3, "%02x", rc); + break; + case Q54SJ108A2_DEBUGFS_VOOV_RESPONSE: + rc = i2c_smbus_read_byte_data(psu->client, PMBUS_VOUT_OV_FAULT_RESPONSE); + if (rc < 0) + return rc; + + rc = snprintf(data, 3, "%02x", rc); + break; + case Q54SJ108A2_DEBUGFS_IOOC_RESPONSE: + rc = i2c_smbus_read_byte_data(psu->client, PMBUS_IOUT_OC_FAULT_RESPONSE); + if (rc < 0) + return rc; + + rc = snprintf(data, 3, "%02x", rc); + break; + case Q54SJ108A2_DEBUGFS_PMBUS_VERSION: + rc = i2c_smbus_read_byte_data(psu->client, PMBUS_REVISION); + if (rc < 0) + return rc; + + rc = snprintf(data, 3, "%02x", rc); + break; + case Q54SJ108A2_DEBUGFS_MFR_ID: + rc = i2c_smbus_read_block_data(psu->client, PMBUS_MFR_ID, data); + if (rc < 0) + return rc; + break; + case Q54SJ108A2_DEBUGFS_MFR_MODEL: + rc = i2c_smbus_read_block_data(psu->client, PMBUS_MFR_MODEL, data); + if (rc < 0) + return rc; + break; + case Q54SJ108A2_DEBUGFS_MFR_REVISION: + rc = i2c_smbus_read_block_data(psu->client, PMBUS_MFR_REVISION, data); + if (rc < 0) + return rc; + break; + case Q54SJ108A2_DEBUGFS_MFR_LOCATION: + rc = i2c_smbus_read_block_data(psu->client, PMBUS_MFR_LOCATION, data); + if (rc < 0) + return rc; + break; + case Q54SJ108A2_DEBUGFS_BLACKBOX_READ_OFFSET: + rc = i2c_smbus_read_byte_data(psu->client, READ_HISTORY_EVENT_NUMBER); + if (rc < 0) + return rc; + + rc = snprintf(data, 3, "%02x", rc); + break; + case Q54SJ108A2_DEBUGFS_BLACKBOX_READ: + rc = i2c_smbus_read_block_data(psu->client, READ_HISTORY_EVENTS, data); + if (rc < 0) + return rc; + + res = bin2hex(data, data_char, 32); + rc = res - data; + + break; + case Q54SJ108A2_DEBUGFS_FLASH_KEY: + rc = i2c_smbus_read_block_data(psu->client, PMBUS_FLASH_KEY_WRITE, data); + if (rc < 0) + return rc; + + res = bin2hex(data, data_char, 4); + rc = res - data; + + break; + default: + return -EINVAL; + } + + data[rc] = '\n'; + rc += 2; + + return simple_read_from_buffer(buf, count, ppos, data, rc); +} + +static ssize_t q54sj108a2_debugfs_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + u8 flash_key[4]; + u8 dst_data; + ssize_t rc; + int *idxp = file->private_data; + int idx = *idxp; + struct q54sj108a2_data *psu = to_psu(idxp, idx); + + rc = i2c_smbus_write_byte_data(psu->client, PMBUS_WRITE_PROTECT, 0); + if (rc) + return rc; + + switch (idx) { + case Q54SJ108A2_DEBUGFS_OPERATION: + rc = kstrtou8_from_user(buf, count, 0, &dst_data); + if (rc < 0) + return rc; + + rc = i2c_smbus_write_byte_data(psu->client, PMBUS_OPERATION, dst_data); + if (rc < 0) + return rc; + + break; + case Q54SJ108A2_DEBUGFS_CLEARFAULT: + rc = i2c_smbus_write_byte(psu->client, PMBUS_CLEAR_FAULTS); + if (rc < 0) + return rc; + + break; + case Q54SJ108A2_DEBUGFS_STOREDEFAULT: + flash_key[0] = 0x7E; + flash_key[1] = 0x15; + flash_key[2] = 0xDC; + flash_key[3] = 0x42; + rc = i2c_smbus_write_block_data(psu->client, PMBUS_FLASH_KEY_WRITE, 4, flash_key); + if (rc < 0) + return rc; + + rc = i2c_smbus_write_byte(psu->client, STORE_DEFAULT_ALL); + if (rc < 0) + return rc; + + break; + case Q54SJ108A2_DEBUGFS_VOOV_RESPONSE: + rc = kstrtou8_from_user(buf, count, 0, &dst_data); + if (rc < 0) + return rc; + + rc = i2c_smbus_write_byte_data(psu->client, PMBUS_VOUT_OV_FAULT_RESPONSE, dst_data); + if (rc < 0) + return rc; + + break; + case Q54SJ108A2_DEBUGFS_IOOC_RESPONSE: + rc = kstrtou8_from_user(buf, count, 0, &dst_data); + if (rc < 0) + return rc; + + rc = i2c_smbus_write_byte_data(psu->client, PMBUS_IOUT_OC_FAULT_RESPONSE, dst_data); + if (rc < 0) + return rc; + + break; + case Q54SJ108A2_DEBUGFS_BLACKBOX_ERASE: + rc = i2c_smbus_write_byte(psu->client, ERASE_BLACKBOX_DATA); + if (rc < 0) + return rc; + + break; + case Q54SJ108A2_DEBUGFS_BLACKBOX_SET_OFFSET: + rc = kstrtou8_from_user(buf, count, 0, &dst_data); + if (rc < 0) + return rc; + + rc = i2c_smbus_write_byte_data(psu->client, SET_HISTORY_EVENT_OFFSET, dst_data); + if (rc < 0) + return rc; + + break; + default: + return -EINVAL; + } + + return count; +} + +static const struct file_operations q54sj108a2_fops = { + .llseek = noop_llseek, + .read = q54sj108a2_debugfs_read, + .write = q54sj108a2_debugfs_write, + .open = simple_open, +}; + +static const struct i2c_device_id q54sj108a2_id[] = { + { "q54sj108a2", q54sj108a2 }, + { }, +}; + +MODULE_DEVICE_TABLE(i2c, q54sj108a2_id); + +static int q54sj108a2_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + u8 buf[I2C_SMBUS_BLOCK_MAX + 1]; + enum chips chip_id; + int ret, i; + struct dentry *debugfs; + struct dentry *q54sj108a2_dir; + struct q54sj108a2_data *psu; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_BLOCK_DATA)) + return -ENODEV; + + if (client->dev.of_node) + chip_id = (enum chips)(unsigned long)of_device_get_match_data(dev); + else + chip_id = i2c_match_id(q54sj108a2_id, client)->driver_data; + + ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf); + if (ret < 0) { + dev_err(&client->dev, "Failed to read Manufacturer ID\n"); + return ret; + } + if (ret != 6 || strncmp(buf, "DELTA", 5)) { + buf[ret] = '\0'; + dev_err(dev, "Unsupported Manufacturer ID '%s'\n", buf); + return -ENODEV; + } + + /* + * The chips support reading PMBUS_MFR_MODEL. + */ + 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 (ret != 14 || strncmp(buf, "Q54SJ108A2", 10)) { + buf[ret] = '\0'; + dev_err(dev, "Unsupported Manufacturer Model '%s'\n", buf); + return -ENODEV; + } + + ret = i2c_smbus_read_block_data(client, PMBUS_MFR_REVISION, buf); + if (ret < 0) { + dev_err(dev, "Failed to read Manufacturer Revision\n"); + return ret; + } + if (ret != 4 || buf[0] != 'S') { + buf[ret] = '\0'; + dev_err(dev, "Unsupported Manufacturer Revision '%s'\n", buf); + return -ENODEV; + } + + ret = pmbus_do_probe(client, &q54sj108a2_info[chip_id]); + if (ret) + return ret; + + psu = devm_kzalloc(&client->dev, sizeof(*psu), GFP_KERNEL); + if (!psu) + return 0; + + psu->client = client; + + debugfs = pmbus_get_debugfs_dir(client); + + q54sj108a2_dir = debugfs_create_dir(client->name, debugfs); + + for (i = 0; i < Q54SJ108A2_DEBUGFS_NUM_ENTRIES; ++i) + psu->debugfs_entries[i] = i; + + debugfs_create_file("operation", 0644, q54sj108a2_dir, + &psu->debugfs_entries[Q54SJ108A2_DEBUGFS_OPERATION], + &q54sj108a2_fops); + debugfs_create_file("clear_fault", 0200, q54sj108a2_dir, + &psu->debugfs_entries[Q54SJ108A2_DEBUGFS_CLEARFAULT], + &q54sj108a2_fops); + debugfs_create_file("write_protect", 0444, q54sj108a2_dir, + &psu->debugfs_entries[Q54SJ108A2_DEBUGFS_WRITEPROTECT], + &q54sj108a2_fops); + debugfs_create_file("store_default", 0200, q54sj108a2_dir, + &psu->debugfs_entries[Q54SJ108A2_DEBUGFS_STOREDEFAULT], + &q54sj108a2_fops); + debugfs_create_file("vo_ov_response", 0644, q54sj108a2_dir, + &psu->debugfs_entries[Q54SJ108A2_DEBUGFS_VOOV_RESPONSE], + &q54sj108a2_fops); + debugfs_create_file("io_oc_response", 0644, q54sj108a2_dir, + &psu->debugfs_entries[Q54SJ108A2_DEBUGFS_IOOC_RESPONSE], + &q54sj108a2_fops); + debugfs_create_file("pmbus_revision", 0444, q54sj108a2_dir, + &psu->debugfs_entries[Q54SJ108A2_DEBUGFS_PMBUS_VERSION], + &q54sj108a2_fops); + debugfs_create_file("mfr_id", 0444, q54sj108a2_dir, + &psu->debugfs_entries[Q54SJ108A2_DEBUGFS_MFR_ID], + &q54sj108a2_fops); + debugfs_create_file("mfr_model", 0444, q54sj108a2_dir, + &psu->debugfs_entries[Q54SJ108A2_DEBUGFS_MFR_MODEL], + &q54sj108a2_fops); + debugfs_create_file("mfr_revision", 0444, q54sj108a2_dir, + &psu->debugfs_entries[Q54SJ108A2_DEBUGFS_MFR_REVISION], + &q54sj108a2_fops); + debugfs_create_file("mfr_location", 0444, q54sj108a2_dir, + &psu->debugfs_entries[Q54SJ108A2_DEBUGFS_MFR_LOCATION], + &q54sj108a2_fops); + debugfs_create_file("blackbox_erase", 0200, q54sj108a2_dir, + &psu->debugfs_entries[Q54SJ108A2_DEBUGFS_BLACKBOX_ERASE], + &q54sj108a2_fops); + debugfs_create_file("blackbox_read_offset", 0444, q54sj108a2_dir, + &psu->debugfs_entries[Q54SJ108A2_DEBUGFS_BLACKBOX_READ_OFFSET], + &q54sj108a2_fops); + debugfs_create_file("blackbox_set_offset", 0200, q54sj108a2_dir, + &psu->debugfs_entries[Q54SJ108A2_DEBUGFS_BLACKBOX_SET_OFFSET], + &q54sj108a2_fops); + debugfs_create_file("blackbox_read", 0444, q54sj108a2_dir, + &psu->debugfs_entries[Q54SJ108A2_DEBUGFS_BLACKBOX_READ], + &q54sj108a2_fops); + debugfs_create_file("flash_key", 0444, q54sj108a2_dir, + &psu->debugfs_entries[Q54SJ108A2_DEBUGFS_FLASH_KEY], + &q54sj108a2_fops); + + return 0; +} + +static const struct of_device_id q54sj108a2_of_match[] = { + { .compatible = "delta,q54sj108a2", .data = (void *)q54sj108a2 }, + { }, +}; + +MODULE_DEVICE_TABLE(of, q54sj108a2_of_match); + +static struct i2c_driver q54sj108a2_driver = { + .driver = { + .name = "q54sj108a2", + .of_match_table = q54sj108a2_of_match, + }, + .probe_new = q54sj108a2_probe, + .id_table = q54sj108a2_id, +}; + +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 000000000..357b9d9d8 --- /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 new file mode 100644 index 000000000..31bb83c0e --- /dev/null +++ b/drivers/hwmon/pmbus/tps40422.c @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for TI TPS40422 + * + * Copyright (c) 2014 Nokia Solutions and Networks. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include "pmbus.h" + +static struct pmbus_driver_info tps40422_info = { + .pages = 2, + .format[PSC_VOLTAGE_IN] = linear, + .format[PSC_VOLTAGE_OUT] = linear, + .format[PSC_TEMPERATURE] = linear, + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_TEMP2 + | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_TEMP + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_TEMP2 + | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_TEMP + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, +}; + +static int tps40422_probe(struct i2c_client *client) +{ + return pmbus_do_probe(client, &tps40422_info); +} + +static const struct i2c_device_id tps40422_id[] = { + {"tps40422", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, tps40422_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver tps40422_driver = { + .driver = { + .name = "tps40422", + }, + .probe_new = tps40422_probe, + .id_table = tps40422_id, +}; + +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 new file mode 100644 index 000000000..81b9d8136 --- /dev/null +++ b/drivers/hwmon/pmbus/tps53679.c @@ -0,0 +1,311 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for Texas Instruments TPS53679 + * + * Copyright (c) 2017 Mellanox Technologies. All rights reserved. + * Copyright (c) 2017 Vadim Pasternak <vadimp@mellanox.com> + */ + +#include <linux/bits.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include "pmbus.h" + +enum chips { + 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 */ +#define TPS53679_PROT_IMVP8_5MV 0x05 /* IMVP8 mode, 5-mV DAC */ +#define TPS53679_PROT_VR13_5MV 0x07 /* VR13.0 mode, 5-mV DAC */ +#define TPS53679_PAGE_NUM 2 + +#define TPS53681_DEVICE_ID 0x81 + +#define TPS53681_PMBUS_REVISION 0x33 + +#define TPS53681_MFR_SPECIFIC_20 0xe4 /* Number of phases, per page */ + +static const struct i2c_device_id tps53679_id[]; + +static int tps53679_identify_mode(struct i2c_client *client, + struct pmbus_driver_info *info) +{ + u8 vout_params; + int i, ret; + + for (i = 0; i < info->pages; i++) { + /* Read the register with VOUT scaling value.*/ + ret = pmbus_read_byte_data(client, i, PMBUS_VOUT_MODE); + if (ret < 0) + return ret; + + vout_params = ret & GENMASK(4, 0); + + switch (vout_params) { + case TPS53679_PROT_VR13_10MV: + case TPS53679_PROT_VR12_5_10MV: + info->vrm_version[i] = vr13; + break; + case TPS53679_PROT_VR13_5MV: + case TPS53679_PROT_VR12_5MV: + case TPS53679_PROT_IMVP8_5MV: + info->vrm_version[i] = vr12; + break; + default: + return -EINVAL; + } + } + + return 0; +} + +static int tps53679_identify_phases(struct i2c_client *client, + struct pmbus_driver_info *info) +{ + int ret; + + /* On TPS53681, only channel A provides per-phase output current */ + ret = pmbus_read_byte_data(client, 0, TPS53681_MFR_SPECIFIC_20); + if (ret < 0) + return ret; + info->phases[0] = (ret & 0x07) + 1; + + return 0; +} + +static int tps53679_identify_chip(struct i2c_client *client, + u8 revision, u16 id) +{ + u8 buf[I2C_SMBUS_BLOCK_MAX]; + int ret; + + ret = pmbus_read_byte_data(client, 0, PMBUS_REVISION); + if (ret < 0) + return ret; + if (ret != revision) { + dev_err(&client->dev, "Unexpected PMBus revision 0x%x\n", ret); + return -ENODEV; + } + + ret = i2c_smbus_read_block_data(client, PMBUS_IC_DEVICE_ID, buf); + if (ret < 0) + return ret; + if (ret != 1 || buf[0] != id) { + dev_err(&client->dev, "Unexpected device ID 0x%x\n", buf[0]); + return -ENODEV; + } + return 0; +} + +/* + * Common identification function for chips with multi-phase support. + * Since those chips have special configuration registers, we want to have + * some level of reassurance that we are really talking with the chip + * being probed. Check PMBus revision and chip ID. + */ +static int tps53679_identify_multiphase(struct i2c_client *client, + struct pmbus_driver_info *info, + int pmbus_rev, int device_id) +{ + int ret; + + ret = tps53679_identify_chip(client, pmbus_rev, device_id); + if (ret < 0) + return ret; + + ret = tps53679_identify_mode(client, info); + if (ret < 0) + return ret; + + return tps53679_identify_phases(client, info); +} + +static int tps53679_identify(struct i2c_client *client, + struct pmbus_driver_info *info) +{ + return tps53679_identify_mode(client, info); +} + +static int tps53681_identify(struct i2c_client *client, + struct pmbus_driver_info *info) +{ + return tps53679_identify_multiphase(client, info, + TPS53681_PMBUS_REVISION, + 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) +{ + /* + * For reading the total output current (READ_IOUT) for all phases, + * the chip datasheet is a bit vague. It says "PHASE must be set to + * FFh to access all phases simultaneously. PHASE may also be set to + * 80h readack (!) the total phase current". + * Experiments show that the command does _not_ report the total + * current for all phases if the phase is set to 0xff. Instead, it + * appears to report the current of one of the phases. Override phase + * parameter with 0x80 when reading the total output current on page 0. + */ + if (reg == PMBUS_READ_IOUT && page == 0 && phase == 0xff) + return pmbus_read_word_data(client, page, 0x80, reg); + return -ENODATA; +} + +static struct pmbus_driver_info tps53679_info = { + .format[PSC_VOLTAGE_IN] = linear, + .format[PSC_VOLTAGE_OUT] = vid, + .format[PSC_TEMPERATURE] = linear, + .format[PSC_CURRENT_OUT] = linear, + .format[PSC_POWER] = linear, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN | PMBUS_HAVE_PIN | + PMBUS_HAVE_STATUS_INPUT | + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_POUT, + .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_POUT, + .pfunc[0] = PMBUS_HAVE_IOUT, + .pfunc[1] = PMBUS_HAVE_IOUT, + .pfunc[2] = PMBUS_HAVE_IOUT, + .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) +{ + struct device *dev = &client->dev; + struct pmbus_driver_info *info; + enum chips chip_id; + + if (dev->of_node) + chip_id = (enum chips)of_device_get_match_data(dev); + else + chip_id = i2c_match_id(tps53679_id, client)->driver_data; + + info = devm_kmemdup(dev, &tps53679_info, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + switch (chip_id) { + case tps53647: + case tps53667: + 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; + info->identify = tps53679_identify; + break; + case tps53681: + info->pages = TPS53679_PAGE_NUM; + info->phases[0] = 6; + info->identify = tps53681_identify; + info->read_word_data = tps53681_read_word_data; + break; + default: + return -ENODEV; + } + + return pmbus_do_probe(client, info); +} + +static const struct i2c_device_id tps53679_id[] = { + {"bmr474", tps53676}, + {"tps53647", tps53647}, + {"tps53667", tps53667}, + {"tps53676", tps53676}, + {"tps53679", tps53679}, + {"tps53681", tps53681}, + {"tps53688", tps53688}, + {} +}; + +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}, + {} +}; +MODULE_DEVICE_TABLE(of, tps53679_of_match); + +static struct i2c_driver tps53679_driver = { + .driver = { + .name = "tps53679", + .of_match_table = of_match_ptr(tps53679_of_match), + }, + .probe_new = tps53679_probe, + .id_table = tps53679_id, +}; + +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/tps546d24.c b/drivers/hwmon/pmbus/tps546d24.c new file mode 100644 index 000000000..435f94304 --- /dev/null +++ b/drivers/hwmon/pmbus/tps546d24.c @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Hardware monitoring driver for TEXAS TPS546D24 buck converter + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pmbus.h> +#include "pmbus.h" + +static struct pmbus_driver_info tps546d24_info = { + .pages = 1, + .format[PSC_VOLTAGE_IN] = linear, + .format[PSC_VOLTAGE_OUT] = linear, + .format[PSC_TEMPERATURE] = linear, + .format[PSC_CURRENT_OUT] = linear, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN + | PMBUS_HAVE_IOUT | PMBUS_HAVE_VOUT + | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, +}; + +static int tps546d24_probe(struct i2c_client *client) +{ + int reg; + + reg = i2c_smbus_read_byte_data(client, PMBUS_VOUT_MODE); + if (reg < 0) + return reg; + + if (reg & 0x80) { + int err; + + err = i2c_smbus_write_byte_data(client, PMBUS_VOUT_MODE, reg & 0x7f); + if (err < 0) + return err; + } + return pmbus_do_probe(client, &tps546d24_info); +} + +static const struct i2c_device_id tps546d24_id[] = { + {"tps546d24", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, tps546d24_id); + +static const struct of_device_id __maybe_unused tps546d24_of_match[] = { + {.compatible = "ti,tps546d24"}, + {} +}; +MODULE_DEVICE_TABLE(of, tps546d24_of_match); + +/* This is the driver that will be inserted */ +static struct i2c_driver tps546d24_driver = { + .driver = { + .name = "tps546d24", + .of_match_table = of_match_ptr(tps546d24_of_match), + }, + .probe_new = tps546d24_probe, + .id_table = tps546d24_id, +}; + +module_i2c_driver(tps546d24_driver); + +MODULE_AUTHOR("Duke Du <dukedu83@gmail.com>"); +MODULE_DESCRIPTION("PMBus driver for TI tps546d24"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/ucd9000.c b/drivers/hwmon/pmbus/ucd9000.c new file mode 100644 index 000000000..3daaf2237 --- /dev/null +++ b/drivers/hwmon/pmbus/ucd9000.c @@ -0,0 +1,707 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for UCD90xxx Sequencer and System Health + * Controller series + * + * Copyright (C) 2011 Ericsson AB. + */ + +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/pmbus.h> +#include <linux/gpio/driver.h> +#include <linux/timekeeping.h> +#include "pmbus.h" + +enum chips { ucd9000, ucd90120, ucd90124, ucd90160, ucd90320, ucd9090, + ucd90910 }; + +#define UCD9000_MONITOR_CONFIG 0xd5 +#define UCD9000_NUM_PAGES 0xd6 +#define UCD9000_FAN_CONFIG_INDEX 0xe7 +#define UCD9000_FAN_CONFIG 0xe8 +#define UCD9000_MFR_STATUS 0xf3 +#define UCD9000_GPIO_SELECT 0xfa +#define UCD9000_GPIO_CONFIG 0xfb +#define UCD9000_DEVICE_ID 0xfd + +/* GPIO CONFIG bits */ +#define UCD9000_GPIO_CONFIG_ENABLE BIT(0) +#define UCD9000_GPIO_CONFIG_OUT_ENABLE BIT(1) +#define UCD9000_GPIO_CONFIG_OUT_VALUE BIT(2) +#define UCD9000_GPIO_CONFIG_STATUS BIT(3) +#define UCD9000_GPIO_INPUT 0 +#define UCD9000_GPIO_OUTPUT 1 + +#define UCD9000_MON_TYPE(x) (((x) >> 5) & 0x07) +#define UCD9000_MON_PAGE(x) ((x) & 0x1f) + +#define UCD9000_MON_VOLTAGE 1 +#define UCD9000_MON_TEMPERATURE 2 +#define UCD9000_MON_CURRENT 3 +#define UCD9000_MON_VOLTAGE_HW 4 + +#define UCD9000_NUM_FAN 4 + +#define UCD9000_GPIO_NAME_LEN 16 +#define UCD9090_NUM_GPIOS 23 +#define UCD901XX_NUM_GPIOS 26 +#define UCD90320_NUM_GPIOS 84 +#define UCD90910_NUM_GPIOS 26 + +#define UCD9000_DEBUGFS_NAME_LEN 24 +#define UCD9000_GPI_COUNT 8 +#define UCD90320_GPI_COUNT 32 + +struct ucd9000_data { + u8 fan_data[UCD9000_NUM_FAN][I2C_SMBUS_BLOCK_MAX]; + struct pmbus_driver_info info; +#ifdef CONFIG_GPIOLIB + struct gpio_chip gpio; +#endif + struct dentry *debugfs; + ktime_t write_time; +}; +#define to_ucd9000_data(_info) container_of(_info, struct ucd9000_data, info) + +struct ucd9000_debugfs_entry { + struct i2c_client *client; + u8 index; +}; + +/* + * It has been observed that the UCD90320 randomly fails register access when + * doing another access right on the back of a register write. To mitigate this + * make sure that there is a minimum delay between a write access and the + * following access. The 250us is based on experimental data. At a delay of + * 200us the issue seems to go away. Add a bit of extra margin to allow for + * system to system differences. + */ +#define UCD90320_WAIT_DELAY_US 250 + +static inline void ucd90320_wait(const struct ucd9000_data *data) +{ + s64 delta = ktime_us_delta(ktime_get(), data->write_time); + + if (delta < UCD90320_WAIT_DELAY_US) + udelay(UCD90320_WAIT_DELAY_US - delta); +} + +static int ucd90320_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 ucd9000_data *data = to_ucd9000_data(info); + + if (reg >= PMBUS_VIRT_BASE) + return -ENXIO; + + ucd90320_wait(data); + return pmbus_read_word_data(client, page, phase, reg); +} + +static int ucd90320_read_byte_data(struct i2c_client *client, int page, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct ucd9000_data *data = to_ucd9000_data(info); + + ucd90320_wait(data); + return pmbus_read_byte_data(client, page, reg); +} + +static int ucd90320_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 ucd9000_data *data = to_ucd9000_data(info); + int ret; + + ucd90320_wait(data); + ret = pmbus_write_word_data(client, page, reg, word); + data->write_time = ktime_get(); + + return ret; +} + +static int ucd90320_write_byte(struct i2c_client *client, int page, u8 value) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct ucd9000_data *data = to_ucd9000_data(info); + int ret; + + ucd90320_wait(data); + ret = pmbus_write_byte(client, page, value); + data->write_time = ktime_get(); + + return ret; +} + +static int ucd9000_get_fan_config(struct i2c_client *client, int fan) +{ + int fan_config = 0; + struct ucd9000_data *data + = to_ucd9000_data(pmbus_get_driver_info(client)); + + if (data->fan_data[fan][3] & 1) + fan_config |= PB_FAN_2_INSTALLED; /* Use lower bit position */ + + /* Pulses/revolution */ + fan_config |= (data->fan_data[fan][3] & 0x06) >> 1; + + return fan_config; +} + +static int ucd9000_read_byte_data(struct i2c_client *client, int page, int reg) +{ + int ret = 0; + int fan_config; + + switch (reg) { + case PMBUS_FAN_CONFIG_12: + if (page > 0) + return -ENXIO; + + ret = ucd9000_get_fan_config(client, 0); + if (ret < 0) + return ret; + fan_config = ret << 4; + ret = ucd9000_get_fan_config(client, 1); + if (ret < 0) + return ret; + fan_config |= ret; + ret = fan_config; + break; + case PMBUS_FAN_CONFIG_34: + if (page > 0) + return -ENXIO; + + ret = ucd9000_get_fan_config(client, 2); + if (ret < 0) + return ret; + fan_config = ret << 4; + ret = ucd9000_get_fan_config(client, 3); + if (ret < 0) + return ret; + fan_config |= ret; + ret = fan_config; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static const struct i2c_device_id ucd9000_id[] = { + {"ucd9000", ucd9000}, + {"ucd90120", ucd90120}, + {"ucd90124", ucd90124}, + {"ucd90160", ucd90160}, + {"ucd90320", ucd90320}, + {"ucd9090", ucd9090}, + {"ucd90910", ucd90910}, + {} +}; +MODULE_DEVICE_TABLE(i2c, ucd9000_id); + +static const struct of_device_id __maybe_unused ucd9000_of_match[] = { + { + .compatible = "ti,ucd9000", + .data = (void *)ucd9000 + }, + { + .compatible = "ti,ucd90120", + .data = (void *)ucd90120 + }, + { + .compatible = "ti,ucd90124", + .data = (void *)ucd90124 + }, + { + .compatible = "ti,ucd90160", + .data = (void *)ucd90160 + }, + { + .compatible = "ti,ucd90320", + .data = (void *)ucd90320 + }, + { + .compatible = "ti,ucd9090", + .data = (void *)ucd9090 + }, + { + .compatible = "ti,ucd90910", + .data = (void *)ucd90910 + }, + { }, +}; +MODULE_DEVICE_TABLE(of, ucd9000_of_match); + +#ifdef CONFIG_GPIOLIB +static int ucd9000_gpio_read_config(struct i2c_client *client, + unsigned int offset) +{ + int ret; + + /* No page set required */ + ret = i2c_smbus_write_byte_data(client, UCD9000_GPIO_SELECT, offset); + if (ret < 0) + return ret; + + return i2c_smbus_read_byte_data(client, UCD9000_GPIO_CONFIG); +} + +static int ucd9000_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) + return ret; + + return !!(ret & UCD9000_GPIO_CONFIG_STATUS); +} + +static void ucd9000_gpio_set(struct gpio_chip *gc, unsigned int offset, + int value) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) { + dev_dbg(&client->dev, "failed to read GPIO %d config: %d\n", + offset, ret); + return; + } + + if (value) { + if (ret & UCD9000_GPIO_CONFIG_STATUS) + return; + + ret |= UCD9000_GPIO_CONFIG_STATUS; + } else { + if (!(ret & UCD9000_GPIO_CONFIG_STATUS)) + return; + + ret &= ~UCD9000_GPIO_CONFIG_STATUS; + } + + ret |= UCD9000_GPIO_CONFIG_ENABLE; + + /* Page set not required */ + ret = i2c_smbus_write_byte_data(client, UCD9000_GPIO_CONFIG, ret); + if (ret < 0) { + dev_dbg(&client->dev, "Failed to write GPIO %d config: %d\n", + offset, ret); + return; + } + + ret &= ~UCD9000_GPIO_CONFIG_ENABLE; + + ret = i2c_smbus_write_byte_data(client, UCD9000_GPIO_CONFIG, ret); + if (ret < 0) + dev_dbg(&client->dev, "Failed to write GPIO %d config: %d\n", + offset, ret); +} + +static int ucd9000_gpio_get_direction(struct gpio_chip *gc, + unsigned int offset) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) + return ret; + + return !(ret & UCD9000_GPIO_CONFIG_OUT_ENABLE); +} + +static int ucd9000_gpio_set_direction(struct gpio_chip *gc, + unsigned int offset, bool direction_out, + int requested_out) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret, config, out_val; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) + return ret; + + if (direction_out) { + out_val = requested_out ? UCD9000_GPIO_CONFIG_OUT_VALUE : 0; + + if (ret & UCD9000_GPIO_CONFIG_OUT_ENABLE) { + if ((ret & UCD9000_GPIO_CONFIG_OUT_VALUE) == out_val) + return 0; + } else { + ret |= UCD9000_GPIO_CONFIG_OUT_ENABLE; + } + + if (out_val) + ret |= UCD9000_GPIO_CONFIG_OUT_VALUE; + else + ret &= ~UCD9000_GPIO_CONFIG_OUT_VALUE; + + } else { + if (!(ret & UCD9000_GPIO_CONFIG_OUT_ENABLE)) + return 0; + + ret &= ~UCD9000_GPIO_CONFIG_OUT_ENABLE; + } + + ret |= UCD9000_GPIO_CONFIG_ENABLE; + config = ret; + + /* Page set not required */ + ret = i2c_smbus_write_byte_data(client, UCD9000_GPIO_CONFIG, config); + if (ret < 0) + return ret; + + config &= ~UCD9000_GPIO_CONFIG_ENABLE; + + return i2c_smbus_write_byte_data(client, UCD9000_GPIO_CONFIG, config); +} + +static int ucd9000_gpio_direction_input(struct gpio_chip *gc, + unsigned int offset) +{ + return ucd9000_gpio_set_direction(gc, offset, UCD9000_GPIO_INPUT, 0); +} + +static int ucd9000_gpio_direction_output(struct gpio_chip *gc, + unsigned int offset, int val) +{ + return ucd9000_gpio_set_direction(gc, offset, UCD9000_GPIO_OUTPUT, + val); +} + +static void ucd9000_probe_gpio(struct i2c_client *client, + const struct i2c_device_id *mid, + struct ucd9000_data *data) +{ + int rc; + + switch (mid->driver_data) { + case ucd9090: + data->gpio.ngpio = UCD9090_NUM_GPIOS; + break; + case ucd90120: + case ucd90124: + case ucd90160: + data->gpio.ngpio = UCD901XX_NUM_GPIOS; + break; + case ucd90320: + data->gpio.ngpio = UCD90320_NUM_GPIOS; + break; + case ucd90910: + data->gpio.ngpio = UCD90910_NUM_GPIOS; + break; + default: + return; /* GPIO support is optional. */ + } + + /* + * Pinmux support has not been added to the new gpio_chip. + * This support should be added when possible given the mux + * behavior of these IO devices. + */ + data->gpio.label = client->name; + data->gpio.get_direction = ucd9000_gpio_get_direction; + data->gpio.direction_input = ucd9000_gpio_direction_input; + data->gpio.direction_output = ucd9000_gpio_direction_output; + data->gpio.get = ucd9000_gpio_get; + data->gpio.set = ucd9000_gpio_set; + data->gpio.can_sleep = true; + data->gpio.base = -1; + data->gpio.parent = &client->dev; + + rc = devm_gpiochip_add_data(&client->dev, &data->gpio, client); + if (rc) + dev_warn(&client->dev, "Could not add gpiochip: %d\n", rc); +} +#else +static void ucd9000_probe_gpio(struct i2c_client *client, + const struct i2c_device_id *mid, + struct ucd9000_data *data) +{ +} +#endif /* CONFIG_GPIOLIB */ + +#ifdef CONFIG_DEBUG_FS +static int ucd9000_get_mfr_status(struct i2c_client *client, u8 *buffer) +{ + int ret = pmbus_set_page(client, 0, 0xff); + + if (ret < 0) + return ret; + + return i2c_smbus_read_block_data(client, UCD9000_MFR_STATUS, buffer); +} + +static int ucd9000_debugfs_show_mfr_status_bit(void *data, u64 *val) +{ + struct ucd9000_debugfs_entry *entry = data; + struct i2c_client *client = entry->client; + u8 buffer[I2C_SMBUS_BLOCK_MAX]; + int ret, i; + + ret = ucd9000_get_mfr_status(client, buffer); + if (ret < 0) + return ret; + + /* + * GPI fault bits are in sets of 8, two bytes from end of response. + */ + i = ret - 3 - entry->index / 8; + if (i >= 0) + *val = !!(buffer[i] & BIT(entry->index % 8)); + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(ucd9000_debugfs_mfr_status_bit, + ucd9000_debugfs_show_mfr_status_bit, NULL, "%1lld\n"); + +static ssize_t ucd9000_debugfs_read_mfr_status(struct file *file, + char __user *buf, size_t count, + loff_t *ppos) +{ + struct i2c_client *client = file->private_data; + u8 buffer[I2C_SMBUS_BLOCK_MAX]; + char str[(I2C_SMBUS_BLOCK_MAX * 2) + 2]; + char *res; + int rc; + + rc = ucd9000_get_mfr_status(client, buffer); + if (rc < 0) + return rc; + + res = bin2hex(str, buffer, min(rc, I2C_SMBUS_BLOCK_MAX)); + *res++ = '\n'; + *res = 0; + + return simple_read_from_buffer(buf, count, ppos, str, res - str); +} + +static const struct file_operations ucd9000_debugfs_show_mfr_status_fops = { + .llseek = noop_llseek, + .read = ucd9000_debugfs_read_mfr_status, + .open = simple_open, +}; + +static int ucd9000_init_debugfs(struct i2c_client *client, + const struct i2c_device_id *mid, + struct ucd9000_data *data) +{ + struct dentry *debugfs; + struct ucd9000_debugfs_entry *entries; + int i, gpi_count; + char name[UCD9000_DEBUGFS_NAME_LEN]; + + debugfs = pmbus_get_debugfs_dir(client); + if (!debugfs) + return -ENOENT; + + data->debugfs = debugfs_create_dir(client->name, debugfs); + if (!data->debugfs) + return -ENOENT; + + /* + * Of the chips this driver supports, only the UCD9090, UCD90160, + * UCD90320, and UCD90910 report GPI faults in their MFR_STATUS + * register, so only create the GPI fault debugfs attributes for those + * chips. + */ + if (mid->driver_data == ucd9090 || mid->driver_data == ucd90160 || + mid->driver_data == ucd90320 || mid->driver_data == ucd90910) { + gpi_count = mid->driver_data == ucd90320 ? UCD90320_GPI_COUNT + : UCD9000_GPI_COUNT; + entries = devm_kcalloc(&client->dev, + gpi_count, sizeof(*entries), + GFP_KERNEL); + if (!entries) + return -ENOMEM; + + for (i = 0; i < gpi_count; i++) { + entries[i].client = client; + entries[i].index = i; + scnprintf(name, UCD9000_DEBUGFS_NAME_LEN, + "gpi%d_alarm", i + 1); + debugfs_create_file(name, 0444, data->debugfs, + &entries[i], + &ucd9000_debugfs_mfr_status_bit); + } + } + + scnprintf(name, UCD9000_DEBUGFS_NAME_LEN, "mfr_status"); + debugfs_create_file(name, 0444, data->debugfs, client, + &ucd9000_debugfs_show_mfr_status_fops); + + return 0; +} +#else +static int ucd9000_init_debugfs(struct i2c_client *client, + const struct i2c_device_id *mid, + struct ucd9000_data *data) +{ + return 0; +} +#endif /* CONFIG_DEBUG_FS */ + +static int ucd9000_probe(struct i2c_client *client) +{ + u8 block_buffer[I2C_SMBUS_BLOCK_MAX + 1]; + struct ucd9000_data *data; + struct pmbus_driver_info *info; + const struct i2c_device_id *mid; + enum chips chip; + int i, ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_BLOCK_DATA)) + return -ENODEV; + + ret = i2c_smbus_read_block_data(client, UCD9000_DEVICE_ID, + block_buffer); + if (ret < 0) { + dev_err(&client->dev, "Failed to read device ID\n"); + return ret; + } + block_buffer[ret] = '\0'; + dev_info(&client->dev, "Device ID %s\n", block_buffer); + + for (mid = ucd9000_id; mid->name[0]; mid++) { + if (!strncasecmp(mid->name, block_buffer, strlen(mid->name))) + break; + } + if (!mid->name[0]) { + dev_err(&client->dev, "Unsupported device\n"); + return -ENODEV; + } + + if (client->dev.of_node) + chip = (enum chips)of_device_get_match_data(&client->dev); + else + chip = mid->driver_data; + + if (chip != ucd9000 && strcmp(client->name, mid->name) != 0) + dev_notice(&client->dev, + "Device mismatch: Configured %s, detected %s\n", + client->name, mid->name); + + data = devm_kzalloc(&client->dev, sizeof(struct ucd9000_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + info = &data->info; + + ret = i2c_smbus_read_byte_data(client, UCD9000_NUM_PAGES); + if (ret < 0) { + dev_err(&client->dev, + "Failed to read number of active pages\n"); + return ret; + } + info->pages = ret; + if (!info->pages) { + dev_err(&client->dev, "No pages configured\n"); + return -ENODEV; + } + + /* The internal temperature sensor is always active */ + info->func[0] = PMBUS_HAVE_TEMP; + + /* Everything else is configurable */ + ret = i2c_smbus_read_block_data(client, UCD9000_MONITOR_CONFIG, + block_buffer); + if (ret <= 0) { + dev_err(&client->dev, "Failed to read configuration data\n"); + return -ENODEV; + } + for (i = 0; i < ret; i++) { + int page = UCD9000_MON_PAGE(block_buffer[i]); + + if (page >= info->pages) + continue; + + switch (UCD9000_MON_TYPE(block_buffer[i])) { + case UCD9000_MON_VOLTAGE: + case UCD9000_MON_VOLTAGE_HW: + info->func[page] |= PMBUS_HAVE_VOUT + | PMBUS_HAVE_STATUS_VOUT; + break; + case UCD9000_MON_TEMPERATURE: + info->func[page] |= PMBUS_HAVE_TEMP2 + | PMBUS_HAVE_STATUS_TEMP; + break; + case UCD9000_MON_CURRENT: + info->func[page] |= PMBUS_HAVE_IOUT + | PMBUS_HAVE_STATUS_IOUT; + break; + default: + break; + } + } + + /* Fan configuration */ + if (mid->driver_data == ucd90124) { + for (i = 0; i < UCD9000_NUM_FAN; i++) { + i2c_smbus_write_byte_data(client, + UCD9000_FAN_CONFIG_INDEX, i); + ret = i2c_smbus_read_block_data(client, + UCD9000_FAN_CONFIG, + data->fan_data[i]); + if (ret < 0) + return ret; + } + i2c_smbus_write_byte_data(client, UCD9000_FAN_CONFIG_INDEX, 0); + + info->read_byte_data = ucd9000_read_byte_data; + info->func[0] |= PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12 + | PMBUS_HAVE_FAN34 | PMBUS_HAVE_STATUS_FAN34; + } else if (mid->driver_data == ucd90320) { + info->read_byte_data = ucd90320_read_byte_data; + info->read_word_data = ucd90320_read_word_data; + info->write_byte = ucd90320_write_byte; + info->write_word_data = ucd90320_write_word_data; + } + + ucd9000_probe_gpio(client, mid, data); + + ret = pmbus_do_probe(client, info); + if (ret) + return ret; + + ret = ucd9000_init_debugfs(client, mid, data); + if (ret) + dev_warn(&client->dev, "Failed to register debugfs: %d\n", + ret); + + return 0; +} + +/* This is the driver that will be inserted */ +static struct i2c_driver ucd9000_driver = { + .driver = { + .name = "ucd9000", + .of_match_table = of_match_ptr(ucd9000_of_match), + }, + .probe_new = ucd9000_probe, + .id_table = ucd9000_id, +}; + +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 new file mode 100644 index 000000000..3ad375a76 --- /dev/null +++ b/drivers/hwmon/pmbus/ucd9200.c @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for ucd9200 series Digital PWM System Controllers + * + * Copyright (C) 2011 Ericsson AB. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/pmbus.h> +#include "pmbus.h" + +#define UCD9200_PHASE_INFO 0xd2 +#define UCD9200_DEVICE_ID 0xfd + +enum chips { ucd9200, ucd9220, ucd9222, ucd9224, ucd9240, ucd9244, ucd9246, + ucd9248 }; + +static const struct i2c_device_id ucd9200_id[] = { + {"ucd9200", ucd9200}, + {"ucd9220", ucd9220}, + {"ucd9222", ucd9222}, + {"ucd9224", ucd9224}, + {"ucd9240", ucd9240}, + {"ucd9244", ucd9244}, + {"ucd9246", ucd9246}, + {"ucd9248", ucd9248}, + {} +}; +MODULE_DEVICE_TABLE(i2c, ucd9200_id); + +static const struct of_device_id __maybe_unused ucd9200_of_match[] = { + { + .compatible = "ti,cd9200", + .data = (void *)ucd9200 + }, + { + .compatible = "ti,cd9220", + .data = (void *)ucd9220 + }, + { + .compatible = "ti,cd9222", + .data = (void *)ucd9222 + }, + { + .compatible = "ti,cd9224", + .data = (void *)ucd9224 + }, + { + .compatible = "ti,cd9240", + .data = (void *)ucd9240 + }, + { + .compatible = "ti,cd9244", + .data = (void *)ucd9244 + }, + { + .compatible = "ti,cd9246", + .data = (void *)ucd9246 + }, + { + .compatible = "ti,cd9248", + .data = (void *)ucd9248 + }, + { }, +}; +MODULE_DEVICE_TABLE(of, ucd9200_of_match); + +static int ucd9200_probe(struct i2c_client *client) +{ + u8 block_buffer[I2C_SMBUS_BLOCK_MAX + 1]; + struct pmbus_driver_info *info; + const struct i2c_device_id *mid; + enum chips chip; + int i, j, ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_BLOCK_DATA)) + return -ENODEV; + + ret = i2c_smbus_read_block_data(client, UCD9200_DEVICE_ID, + block_buffer); + if (ret < 0) { + dev_err(&client->dev, "Failed to read device ID\n"); + return ret; + } + block_buffer[ret] = '\0'; + dev_info(&client->dev, "Device ID %s\n", block_buffer); + + for (mid = ucd9200_id; mid->name[0]; mid++) { + if (!strncasecmp(mid->name, block_buffer, strlen(mid->name))) + break; + } + if (!mid->name[0]) { + dev_err(&client->dev, "Unsupported device\n"); + return -ENODEV; + } + + if (client->dev.of_node) + chip = (enum chips)of_device_get_match_data(&client->dev); + else + chip = mid->driver_data; + + if (chip != ucd9200 && strcmp(client->name, mid->name) != 0) + dev_notice(&client->dev, + "Device mismatch: Configured %s, detected %s\n", + client->name, mid->name); + + info = devm_kzalloc(&client->dev, sizeof(struct pmbus_driver_info), + GFP_KERNEL); + if (!info) + return -ENOMEM; + + ret = i2c_smbus_read_block_data(client, UCD9200_PHASE_INFO, + block_buffer); + if (ret < 0) { + dev_err(&client->dev, "Failed to read phase information\n"); + return ret; + } + + /* + * Calculate number of configured pages (rails) from PHASE_INFO + * register. + * Rails have to be sequential, so we can abort after finding + * the first unconfigured rail. + */ + info->pages = 0; + for (i = 0; i < ret; i++) { + if (!block_buffer[i]) + break; + info->pages++; + } + if (!info->pages) { + dev_err(&client->dev, "No rails configured\n"); + return -ENODEV; + } + dev_info(&client->dev, "%d rails configured\n", info->pages); + + /* + * Set PHASE registers on all pages to 0xff to ensure that phase + * specific commands will apply to all phases of a given page (rail). + * This only affects the READ_IOUT and READ_TEMPERATURE2 registers. + * READ_IOUT will return the sum of currents of all phases of a rail, + * and READ_TEMPERATURE2 will return the maximum temperature detected + * for the phases of the rail. + */ + for (i = 0; i < info->pages; i++) { + /* + * Setting PAGE & PHASE fails once in a while for no obvious + * reason, so we need to retry a couple of times. + */ + for (j = 0; j < 3; j++) { + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, i); + if (ret < 0) + continue; + ret = i2c_smbus_write_byte_data(client, PMBUS_PHASE, + 0xff); + if (ret < 0) + continue; + break; + } + if (ret < 0) { + dev_err(&client->dev, + "Failed to initialize PHASE registers\n"); + return ret; + } + } + if (info->pages > 1) + i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); + + info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT | + PMBUS_HAVE_IIN | PMBUS_HAVE_PIN | + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP | + PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP; + + for (i = 1; i < info->pages; i++) + info->func[i] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_POUT | + PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP; + + /* ucd9240 supports a single fan */ + if (mid->driver_data == ucd9240) + info->func[0] |= PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12; + + return pmbus_do_probe(client, info); +} + +/* This is the driver that will be inserted */ +static struct i2c_driver ucd9200_driver = { + .driver = { + .name = "ucd9200", + .of_match_table = of_match_ptr(ucd9200_of_match), + }, + .probe_new = ucd9200_probe, + .id_table = ucd9200_id, +}; + +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 new file mode 100644 index 000000000..32bc7736d --- /dev/null +++ b/drivers/hwmon/pmbus/xdpe12284.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for Infineon Multi-phase Digital VR Controllers + * + * Copyright (c) 2020 Mellanox Technologies. All rights reserved. + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/regulator/driver.h> + +#include "pmbus.h" + +#define XDPE122_PROT_VR12_5MV 0x01 /* VR12.0 mode, 5-mV DAC */ +#define XDPE122_PROT_VR12_5_10MV 0x02 /* VR12.5 mode, 10-mV DAC */ +#define XDPE122_PROT_IMVP9_10MV 0x03 /* IMVP9 mode, 10-mV DAC */ +#define XDPE122_AMD_625MV 0x10 /* AMD mode 6.25mV */ +#define XDPE122_PAGE_NUM 2 + +static int xdpe122_read_word_data(struct i2c_client *client, int page, + int phase, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + long val; + s16 exponent; + s32 mantissa; + int ret; + + switch (reg) { + 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; + + /* Convert register value to LINEAR11 data. */ + exponent = ((s16)ret) >> 11; + mantissa = ((s16)((ret & GENMASK(10, 0)) << 5)) >> 5; + val = mantissa * 1000L; + if (exponent >= 0) + val <<= exponent; + else + val >>= -exponent; + + /* Convert data to VID register. */ + switch (info->vrm_version[page]) { + case vr13: + if (val >= 500) + return 1 + DIV_ROUND_CLOSEST(val - 500, 10); + return 0; + case vr12: + if (val >= 250) + return 1 + DIV_ROUND_CLOSEST(val - 250, 5); + return 0; + case imvp9: + if (val >= 200) + return 1 + DIV_ROUND_CLOSEST(val - 200, 10); + return 0; + case amd625mv: + if (val >= 200 && val <= 1550) + return DIV_ROUND_CLOSEST((1550 - val) * 100, + 625); + return 0; + default: + return -EINVAL; + } + default: + return -ENODATA; + } + + return 0; +} + +static int xdpe122_identify(struct i2c_client *client, + struct pmbus_driver_info *info) +{ + u8 vout_params; + int i, ret, vout_mode; + + vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE); + if (vout_mode >= 0 && vout_mode != 0xff) { + switch (vout_mode >> 5) { + case 0: + info->format[PSC_VOLTAGE_OUT] = linear; + return 0; + case 1: + info->format[PSC_VOLTAGE_OUT] = vid; + info->read_word_data = xdpe122_read_word_data; + break; + default: + return -ENODEV; + } + } + + for (i = 0; i < XDPE122_PAGE_NUM; i++) { + /* Read the register with VOUT scaling value.*/ + ret = pmbus_read_byte_data(client, i, PMBUS_VOUT_MODE); + if (ret < 0) + return ret; + + vout_params = ret & GENMASK(4, 0); + + switch (vout_params) { + case XDPE122_PROT_VR12_5_10MV: + info->vrm_version[i] = vr13; + break; + case XDPE122_PROT_VR12_5MV: + info->vrm_version[i] = vr12; + break; + case XDPE122_PROT_IMVP9_10MV: + info->vrm_version[i] = imvp9; + break; + case XDPE122_AMD_625MV: + info->vrm_version[i] = amd625mv; + break; + default: + return -EINVAL; + } + } + + return 0; +} + +static const struct regulator_desc __maybe_unused xdpe122_reg_desc[] = { + PMBUS_REGULATOR("vout", 0), + PMBUS_REGULATOR("vout", 1), +}; + +static struct pmbus_driver_info xdpe122_info = { + .pages = XDPE122_PAGE_NUM, + .format[PSC_VOLTAGE_IN] = linear, + .format[PSC_TEMPERATURE] = linear, + .format[PSC_CURRENT_IN] = linear, + .format[PSC_CURRENT_OUT] = linear, + .format[PSC_POWER] = linear, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT, + .func[1] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT, + .identify = xdpe122_identify, +#if IS_ENABLED(CONFIG_SENSORS_XDPE122_REGULATOR) + .num_regulators = 2, + .reg_desc = xdpe122_reg_desc, +#endif +}; + +static int xdpe122_probe(struct i2c_client *client) +{ + struct pmbus_driver_info *info; + + info = devm_kmemdup(&client->dev, &xdpe122_info, sizeof(*info), + GFP_KERNEL); + if (!info) + return -ENOMEM; + + return pmbus_do_probe(client, info); +} + +static const struct i2c_device_id xdpe122_id[] = { + {"xdpe11280", 0}, + {"xdpe12254", 0}, + {"xdpe12284", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, xdpe122_id); + +static const struct of_device_id __maybe_unused xdpe122_of_match[] = { + {.compatible = "infineon,xdpe11280"}, + {.compatible = "infineon,xdpe12254"}, + {.compatible = "infineon,xdpe12284"}, + {} +}; +MODULE_DEVICE_TABLE(of, xdpe122_of_match); + +static struct i2c_driver xdpe122_driver = { + .driver = { + .name = "xdpe12284", + .of_match_table = of_match_ptr(xdpe122_of_match), + }, + .probe_new = xdpe122_probe, + .id_table = xdpe122_id, +}; + +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/xdpe152c4.c b/drivers/hwmon/pmbus/xdpe152c4.c new file mode 100644 index 000000000..b8a36ef73 --- /dev/null +++ b/drivers/hwmon/pmbus/xdpe152c4.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for Infineon Multi-phase Digital VR Controllers + * + * Copyright (c) 2022 Infineon Technologies. All rights reserved. + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include "pmbus.h" + +#define XDPE152_PAGE_NUM 2 + +static struct pmbus_driver_info xdpe152_info = { + .pages = XDPE152_PAGE_NUM, + .format[PSC_VOLTAGE_IN] = linear, + .format[PSC_VOLTAGE_OUT] = linear, + .format[PSC_TEMPERATURE] = linear, + .format[PSC_CURRENT_IN] = linear, + .format[PSC_CURRENT_OUT] = linear, + .format[PSC_POWER] = linear, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT, + .func[1] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT, +}; + +static int xdpe152_probe(struct i2c_client *client) +{ + struct pmbus_driver_info *info; + + info = devm_kmemdup(&client->dev, &xdpe152_info, sizeof(*info), + GFP_KERNEL); + if (!info) + return -ENOMEM; + + return pmbus_do_probe(client, info); +} + +static const struct i2c_device_id xdpe152_id[] = { + {"xdpe152c4", 0}, + {"xdpe15284", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, xdpe152_id); + +static const struct of_device_id __maybe_unused xdpe152_of_match[] = { + {.compatible = "infineon,xdpe152c4"}, + {.compatible = "infineon,xdpe15284"}, + {} +}; +MODULE_DEVICE_TABLE(of, xdpe152_of_match); + +static struct i2c_driver xdpe152_driver = { + .driver = { + .name = "xdpe152c4", + .of_match_table = of_match_ptr(xdpe152_of_match), + }, + .probe_new = xdpe152_probe, + .id_table = xdpe152_id, +}; + +module_i2c_driver(xdpe152_driver); + +MODULE_AUTHOR("Greg Schwendimann <greg.schwendimann@infineon.com>"); +MODULE_DESCRIPTION("PMBus driver for Infineon XDPE152 family"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/zl6100.c b/drivers/hwmon/pmbus/zl6100.c new file mode 100644 index 000000000..e9df0c56d --- /dev/null +++ b/drivers/hwmon/pmbus/zl6100.c @@ -0,0 +1,473 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for ZL6100 and compatibles + * + * Copyright (c) 2011 Ericsson AB. + * Copyright (c) 2012 Guenter Roeck + */ + +#include <linux/bitops.h> +#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 "pmbus.h" + +enum chips { zl2004, zl2005, zl2006, zl2008, zl2105, zl2106, zl6100, zl6105, + zl8802, zl9101, zl9117, zls1003, zls4009 }; + +struct zl6100_data { + int id; + ktime_t access; /* chip access time */ + int delay; /* Delay between chip accesses in uS */ + struct pmbus_driver_info info; +}; + +#define to_zl6100_data(x) container_of(x, struct zl6100_data, info) + +#define ZL6100_MFR_CONFIG 0xd0 +#define ZL6100_DEVICE_ID 0xe4 + +#define ZL6100_MFR_XTEMP_ENABLE BIT(7) + +#define ZL8802_MFR_USER_GLOBAL_CONFIG 0xe9 +#define ZL8802_MFR_TMON_ENABLE BIT(12) +#define ZL8802_MFR_USER_CONFIG 0xd1 +#define ZL8802_MFR_XTEMP_ENABLE_2 BIT(1) +#define ZL8802_MFR_DDC_CONFIG 0xd3 +#define ZL8802_MFR_PHASES_MASK 0x0007 + +#define MFR_VMON_OV_FAULT_LIMIT 0xf5 +#define MFR_VMON_UV_FAULT_LIMIT 0xf6 +#define MFR_READ_VMON 0xf7 + +#define VMON_UV_WARNING BIT(5) +#define VMON_OV_WARNING BIT(4) +#define VMON_UV_FAULT BIT(1) +#define VMON_OV_FAULT BIT(0) + +#define ZL6100_WAIT_TIME 1000 /* uS */ + +static ushort delay = ZL6100_WAIT_TIME; +module_param(delay, ushort, 0644); +MODULE_PARM_DESC(delay, "Delay between chip accesses in uS"); + +/* Convert linear sensor value to milli-units */ +static long zl6100_l2d(s16 l) +{ + s16 exponent; + s32 mantissa; + long val; + + exponent = l >> 11; + mantissa = ((s16)((l & 0x7ff) << 5)) >> 5; + + val = mantissa; + + /* scale result to milli-units */ + val = val * 1000L; + + if (exponent >= 0) + val <<= exponent; + else + val >>= -exponent; + + return val; +} + +#define MAX_MANTISSA (1023 * 1000) +#define MIN_MANTISSA (511 * 1000) + +static u16 zl6100_d2l(long val) +{ + s16 exponent = 0, mantissa; + bool negative = false; + + /* simple case */ + if (val == 0) + return 0; + + if (val < 0) { + negative = true; + val = -val; + } + + /* Reduce large mantissa until it fits into 10 bit */ + while (val >= MAX_MANTISSA && exponent < 15) { + exponent++; + val >>= 1; + } + /* Increase small mantissa to improve precision */ + while (val < MIN_MANTISSA && exponent > -15) { + exponent--; + val <<= 1; + } + + /* Convert mantissa from milli-units to units */ + mantissa = DIV_ROUND_CLOSEST(val, 1000); + + /* Ensure that resulting number is within range */ + if (mantissa > 0x3ff) + mantissa = 0x3ff; + + /* restore sign */ + if (negative) + mantissa = -mantissa; + + /* Convert to 5 bit exponent, 11 bit mantissa */ + return (mantissa & 0x7ff) | ((exponent << 11) & 0xf800); +} + +/* Some chips need a delay between accesses */ +static inline void zl6100_wait(const struct zl6100_data *data) +{ + if (data->delay) { + s64 delta = ktime_us_delta(ktime_get(), data->access); + if (delta < data->delay) + udelay(data->delay - delta); + } +} + +static int zl6100_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 zl6100_data *data = to_zl6100_data(info); + int ret, vreg; + + if (page >= info->pages) + return -ENXIO; + + if (data->id == zl2005) { + /* + * Limit register detection is not reliable on ZL2005. + * Make sure registers are not erroneously detected. + */ + switch (reg) { + case PMBUS_VOUT_OV_WARN_LIMIT: + case PMBUS_VOUT_UV_WARN_LIMIT: + case PMBUS_IOUT_OC_WARN_LIMIT: + return -ENXIO; + } + } + + switch (reg) { + case PMBUS_VIRT_READ_VMON: + vreg = MFR_READ_VMON; + break; + case PMBUS_VIRT_VMON_OV_WARN_LIMIT: + case PMBUS_VIRT_VMON_OV_FAULT_LIMIT: + vreg = MFR_VMON_OV_FAULT_LIMIT; + break; + case PMBUS_VIRT_VMON_UV_WARN_LIMIT: + case PMBUS_VIRT_VMON_UV_FAULT_LIMIT: + vreg = MFR_VMON_UV_FAULT_LIMIT; + break; + default: + if (reg >= PMBUS_VIRT_BASE) + return -ENXIO; + vreg = reg; + break; + } + + zl6100_wait(data); + ret = pmbus_read_word_data(client, page, phase, vreg); + data->access = ktime_get(); + if (ret < 0) + return ret; + + switch (reg) { + case PMBUS_VIRT_VMON_OV_WARN_LIMIT: + ret = zl6100_d2l(DIV_ROUND_CLOSEST(zl6100_l2d(ret) * 9, 10)); + break; + case PMBUS_VIRT_VMON_UV_WARN_LIMIT: + ret = zl6100_d2l(DIV_ROUND_CLOSEST(zl6100_l2d(ret) * 11, 10)); + break; + } + + return ret; +} + +static int zl6100_read_byte_data(struct i2c_client *client, int page, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct zl6100_data *data = to_zl6100_data(info); + int ret, status; + + if (page >= info->pages) + return -ENXIO; + + zl6100_wait(data); + + switch (reg) { + case PMBUS_VIRT_STATUS_VMON: + ret = pmbus_read_byte_data(client, 0, + PMBUS_STATUS_MFR_SPECIFIC); + if (ret < 0) + break; + + status = 0; + if (ret & VMON_UV_WARNING) + status |= PB_VOLTAGE_UV_WARNING; + if (ret & VMON_OV_WARNING) + status |= PB_VOLTAGE_OV_WARNING; + if (ret & VMON_UV_FAULT) + status |= PB_VOLTAGE_UV_FAULT; + if (ret & VMON_OV_FAULT) + status |= PB_VOLTAGE_OV_FAULT; + ret = status; + break; + default: + ret = pmbus_read_byte_data(client, page, reg); + break; + } + data->access = ktime_get(); + + return ret; +} + +static int zl6100_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 zl6100_data *data = to_zl6100_data(info); + int ret, vreg; + + if (page >= info->pages) + return -ENXIO; + + switch (reg) { + case PMBUS_VIRT_VMON_OV_WARN_LIMIT: + word = zl6100_d2l(DIV_ROUND_CLOSEST(zl6100_l2d(word) * 10, 9)); + vreg = MFR_VMON_OV_FAULT_LIMIT; + pmbus_clear_cache(client); + break; + case PMBUS_VIRT_VMON_OV_FAULT_LIMIT: + vreg = MFR_VMON_OV_FAULT_LIMIT; + pmbus_clear_cache(client); + break; + case PMBUS_VIRT_VMON_UV_WARN_LIMIT: + word = zl6100_d2l(DIV_ROUND_CLOSEST(zl6100_l2d(word) * 10, 11)); + vreg = MFR_VMON_UV_FAULT_LIMIT; + pmbus_clear_cache(client); + break; + case PMBUS_VIRT_VMON_UV_FAULT_LIMIT: + vreg = MFR_VMON_UV_FAULT_LIMIT; + pmbus_clear_cache(client); + break; + default: + if (reg >= PMBUS_VIRT_BASE) + return -ENXIO; + vreg = reg; + } + + zl6100_wait(data); + ret = pmbus_write_word_data(client, page, vreg, word); + data->access = ktime_get(); + + return ret; +} + +static int zl6100_write_byte(struct i2c_client *client, int page, u8 value) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct zl6100_data *data = to_zl6100_data(info); + int ret; + + if (page >= info->pages) + return -ENXIO; + + zl6100_wait(data); + ret = pmbus_write_byte(client, page, value); + data->access = ktime_get(); + + return ret; +} + +static const struct i2c_device_id zl6100_id[] = { + {"bmr450", zl2005}, + {"bmr451", zl2005}, + {"bmr462", zl2008}, + {"bmr463", zl2008}, + {"bmr464", zl2008}, + {"bmr465", zls4009}, + {"bmr466", zls1003}, + {"bmr467", zls4009}, + {"bmr469", zl8802}, + {"zl2004", zl2004}, + {"zl2005", zl2005}, + {"zl2006", zl2006}, + {"zl2008", zl2008}, + {"zl2105", zl2105}, + {"zl2106", zl2106}, + {"zl6100", zl6100}, + {"zl6105", zl6105}, + {"zl8802", zl8802}, + {"zl9101", zl9101}, + {"zl9117", zl9117}, + {"zls1003", zls1003}, + {"zls4009", zls4009}, + { } +}; +MODULE_DEVICE_TABLE(i2c, zl6100_id); + +static int zl6100_probe(struct i2c_client *client) +{ + int ret, i; + struct zl6100_data *data; + struct pmbus_driver_info *info; + u8 device_id[I2C_SMBUS_BLOCK_MAX + 1]; + const struct i2c_device_id *mid; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_WORD_DATA + | I2C_FUNC_SMBUS_READ_BLOCK_DATA)) + return -ENODEV; + + ret = i2c_smbus_read_block_data(client, ZL6100_DEVICE_ID, + device_id); + if (ret < 0) { + dev_err(&client->dev, "Failed to read device ID\n"); + return ret; + } + device_id[ret] = '\0'; + dev_info(&client->dev, "Device ID %s\n", device_id); + + mid = NULL; + for (mid = zl6100_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; + } + if (strcmp(client->name, mid->name) != 0) + dev_notice(&client->dev, + "Device mismatch: Configured %s, detected %s\n", + client->name, mid->name); + + data = devm_kzalloc(&client->dev, sizeof(struct zl6100_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->id = mid->driver_data; + + /* + * According to information from the chip vendor, all currently + * supported chips are known to require a wait time between I2C + * accesses. + */ + data->delay = delay; + + /* + * Since there was a direct I2C device access above, wait before + * accessing the chip again. + */ + data->access = ktime_get(); + zl6100_wait(data); + + info = &data->info; + + info->pages = 1; + info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT + | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + + /* + * ZL2004, ZL8802, ZL9101M, ZL9117M and ZLS4009 support monitoring + * an extra voltage (VMON for ZL2004, ZL8802 and ZLS4009, + * VDRV for ZL9101M and ZL9117M). Report it as vmon. + */ + if (data->id == zl2004 || data->id == zl8802 || data->id == zl9101 || + data->id == zl9117 || data->id == zls4009) + info->func[0] |= PMBUS_HAVE_VMON | PMBUS_HAVE_STATUS_VMON; + + /* + * ZL8802 has two outputs that can be used either independently or in + * a current sharing configuration. The driver uses the DDC_CONFIG + * register to check if the module is running with independent or + * shared outputs. If the module is in shared output mode, only one + * output voltage will be reported. + */ + if (data->id == zl8802) { + info->pages = 2; + info->func[0] |= PMBUS_HAVE_IIN; + + ret = i2c_smbus_read_word_data(client, ZL8802_MFR_DDC_CONFIG); + if (ret < 0) + return ret; + + data->access = ktime_get(); + zl6100_wait(data); + + if (ret & ZL8802_MFR_PHASES_MASK) + info->func[1] |= PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT; + else + info->func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT; + + for (i = 0; i < 2; i++) { + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, i); + if (ret < 0) + return ret; + + data->access = ktime_get(); + zl6100_wait(data); + + ret = i2c_smbus_read_word_data(client, ZL8802_MFR_USER_CONFIG); + if (ret < 0) + return ret; + + if (ret & ZL8802_MFR_XTEMP_ENABLE_2) + info->func[i] |= PMBUS_HAVE_TEMP2; + + data->access = ktime_get(); + zl6100_wait(data); + } + ret = i2c_smbus_read_word_data(client, ZL8802_MFR_USER_GLOBAL_CONFIG); + if (ret < 0) + return ret; + + if (ret & ZL8802_MFR_TMON_ENABLE) + info->func[0] |= PMBUS_HAVE_TEMP3; + } else { + ret = i2c_smbus_read_word_data(client, ZL6100_MFR_CONFIG); + if (ret < 0) + return ret; + + if (ret & ZL6100_MFR_XTEMP_ENABLE) + info->func[0] |= PMBUS_HAVE_TEMP2; + } + + data->access = ktime_get(); + zl6100_wait(data); + + info->read_word_data = zl6100_read_word_data; + info->read_byte_data = zl6100_read_byte_data; + info->write_word_data = zl6100_write_word_data; + info->write_byte = zl6100_write_byte; + + return pmbus_do_probe(client, info); +} + +static struct i2c_driver zl6100_driver = { + .driver = { + .name = "zl6100", + }, + .probe_new = zl6100_probe, + .id_table = zl6100_id, +}; + +module_i2c_driver(zl6100_driver); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for ZL6100 and compatibles"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); |