diff mbox series

hwmon: (pmbus) Driver for Maxim MAX16601

Message ID 20200506214949.175744-1-linux@roeck-us.net
State Superseded
Headers show
Series hwmon: (pmbus) Driver for Maxim MAX16601 | expand

Commit Message

Guenter Roeck May 6, 2020, 9:49 p.m. UTC
MAX16601 is a VR13.HC Dual-Output Voltage Regulator Chipset,
implementing a (8+1) multiphase synchronous buck converter.

Cc: Alex Qiu <xqiu@google.com>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
---
 Documentation/hwmon/index.rst    |   1 +
 Documentation/hwmon/max16601.rst | 159 ++++++++++++++++
 drivers/hwmon/pmbus/Kconfig      |   9 +
 drivers/hwmon/pmbus/Makefile     |   1 +
 drivers/hwmon/pmbus/max16601.c   | 314 +++++++++++++++++++++++++++++++
 5 files changed, 484 insertions(+)
 create mode 100644 Documentation/hwmon/max16601.rst
 create mode 100644 drivers/hwmon/pmbus/max16601.c

Comments

Alex Qiu May 8, 2020, 12:54 a.m. UTC | #1
Hi Guenter,

The drivers look good on our system, although more tests are ongoing.
The patch LGTM with minor nit. Thank you very much!

On Wed, May 6, 2020 at 2:49 PM Guenter Roeck <linux@roeck-us.net> wrote:
>
> MAX16601 is a VR13.HC Dual-Output Voltage Regulator Chipset,
> implementing a (8+1) multiphase synchronous buck converter.
>
> Cc: Alex Qiu <xqiu@google.com>
> Signed-off-by: Guenter Roeck <linux@roeck-us.net>
> ---
>  Documentation/hwmon/index.rst    |   1 +
>  Documentation/hwmon/max16601.rst | 159 ++++++++++++++++
>  drivers/hwmon/pmbus/Kconfig      |   9 +
>  drivers/hwmon/pmbus/Makefile     |   1 +
>  drivers/hwmon/pmbus/max16601.c   | 314 +++++++++++++++++++++++++++++++
>  5 files changed, 484 insertions(+)
>  create mode 100644 Documentation/hwmon/max16601.rst
>  create mode 100644 drivers/hwmon/pmbus/max16601.c
>
> diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
> index 8ef62fd39787..df87ae15a1d4 100644
> --- a/Documentation/hwmon/index.rst
> +++ b/Documentation/hwmon/index.rst
> @@ -106,6 +106,7 @@ Hardware Monitoring Kernel Drivers
>     max16064
>     max16065
>     max1619
> +   max16601
>     max1668
>     max197
>     max20730
> diff --git a/Documentation/hwmon/max16601.rst b/Documentation/hwmon/max16601.rst
> new file mode 100644
> index 000000000000..346e74674c51
> --- /dev/null
> +++ b/Documentation/hwmon/max16601.rst
> @@ -0,0 +1,159 @@
> +.. SPDX-License-Identifier: GPL-2.0
> +
> +Kernel driver max16601
> +======================
> +
> +Supported chips:
> +
> +  * Maxim MAX16601
> +
> +    Prefix: 'max16601'
> +
> +    Addresses scanned: -
> +
> +    Datasheet: Not published
> +
> +Author: Guenter Roeck <linux@roeck-us.net>
> +
> +
> +Description
> +-----------
> +
> +This driver supports the MAX16601 VR13.HC Dual-Output Voltage Regulator
> +Chipset.
> +
> +The driver is a client driver to the core PMBus driver.
> +Please see Documentation/hwmon/pmbus.rst for details on PMBus client drivers.
> +
> +
> +Usage Notes
> +-----------
> +
> +This driver does not auto-detect devices. You will have to instantiate the
> +devices explicitly. Please see Documentation/i2c/instantiating-devices.rst for
> +details.
> +
> +
> +Platform data support
> +---------------------
> +
> +The driver supports standard PMBus driver platform data.
> +
> +
> +Sysfs entries
> +-------------
> +
> +The following attributes are supported.
> +
> +======================= =======================================================
> +in1_label              "vin1"
> +in1_input              VCORE input voltage.
> +in1_alarm              Input voltage alarm.
> +
> +in2_label              "vout1"
> +in2_input              VCORE output voltage.
> +in2_alarm              Output voltage alarm.
> +
> +curr1_label            "iin1"
> +curr1_input            VCORE input current, derived from duty cycle and output
> +                       current.
> +curr1_max              Maximum input current.
> +curr1_max_alarm                Current high alarm.
> +
> +curr2_label            "iin1.0"
> +curr2_input            VCORE phase 0 input current.
> +
> +curr3_label            "iin1.1"
> +curr3_input            VCORE phase 1 input current.
> +
> +curr4_label            "iin1.2"
> +curr4_input            VCORE phase 2 input current.
> +
> +curr5_label            "iin1.3"
> +curr5_input            VCORE phase 3 input current.
> +
> +curr6_label            "iin1.4"
> +curr6_input            VCORE phase 4 input current.
> +
> +curr7_label            "iin1.5"
> +curr7_input            VCORE phase 5 input current.
> +
> +curr8_label            "iin1.6"
> +curr8_input            VCORE phase 6 input current.
> +
> +curr9_label            "iin1.7"
> +curr9_input            VCORE phase 7 input current.
> +
> +curr10_label           "iin2"
> +curr10_input           VCORE input current, derived from sensor element.
> +
> +curr11_label           "iin3"
> +curr11_input           VSA input current.
> +
> +curr12_label           "iout1"
> +curr12_input           VCORE output current.
> +curr12_crit            Critical output current.
> +curr12_crit_alarm      Output current critical alarm.
> +curr12_max             Maximum output current.
> +curr12_max_alarm       Output current high alarm.
> +
> +curr13_label           "iout1.0"
> +curr13_input           VCORE phase 0 output current.
> +
> +curr14_label           "iout1.1"
> +curr14_input           VCORE phase 1 output current.
> +
> +curr15_label           "iout1.2"
> +curr15_input           VCORE phase 2 output current.
> +
> +curr16_label           "iout1.3"
> +curr16_input           VCORE phase 3 output current.
> +
> +curr17_label           "iout1.4"
> +curr17_input           VCORE phase 4 output current.
> +
> +curr18_label           "iout1.5"
> +curr18_input           VCORE phase 5 output current.
> +
> +curr19_label           "iout1.6"
> +curr19_input           VCORE phase 6 output current.
> +
> +curr20_label           "iout1.7"
> +curr20_input           VCORE phase 7 output current.
> +
> +curr21_label           "iout3"
> +curr21_input           VSA output current.
> +curr21_highest         Historical maximum VSA output current.
> +curr21_reset_history   Write any value to reset curr21_highest.
> +curr21_crit            Critical output current.
> +curr21_crit_alarm      Output current critical alarm.
> +curr21_max             Maximum output current.
> +curr21_max_alarm       Output current high alarm.
> +
> +power1_label           "pin1"
> +power1_input           Input power, derived from duty cycle and output current.
> +power1_alarm           Input power alarm.
> +
> +power2_label           "pin2"
> +power2_input           Input power, derived from input current sensor.
> +
> +power3_label           "pout"
> +power3_input           Output power.
> +
> +temp1_input            VCORE temperature.
> +temp1_crit             Critical high temperature.
> +temp1_crit_alarm       Chip temperature critical high alarm.
> +temp1_max              Maximum temperature.
> +temp1_max_alarm                Chip temperature high alarm.
> +
> +temp2_input            TSENSE_0 temperature
> +temp3_input            TSENSE_1 temperature
> +temp4_input            TSENSE_2 temperature
> +temp5_input            TSENSE_3 temperature
> +
> +temp6_input            VSA temperature.
> +temp6_crit             Critical high temperature.
> +temp6_crit_alarm       Chip temperature critical high alarm.
> +temp6_max              Maximum temperature.
> +temp6_max_alarm                Chip temperature high alarm.
> +======================= =======================================================
> diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig
> index de12a565006d..a337195b1c39 100644
> --- a/drivers/hwmon/pmbus/Kconfig
> +++ b/drivers/hwmon/pmbus/Kconfig
> @@ -146,6 +146,15 @@ config SENSORS_MAX16064
>           This driver can also be built as a module. If so, the module will
>           be called max16064.
>
> +config SENSORS_MAX16601
> +       tristate "Maxim MAX16601"
> +       help
> +         If you say yes here you get hardware monitoring support for Maxim
> +         MAX16601.
> +
> +         This driver can also be built as a module. If so, the module will
> +         be called max16601.
> +
>  config SENSORS_MAX20730
>         tristate "Maxim MAX20730, MAX20734, MAX20743"
>         help
> diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile
> index 5feb45806123..c4b15db996ad 100644
> --- a/drivers/hwmon/pmbus/Makefile
> +++ b/drivers/hwmon/pmbus/Makefile
> @@ -17,6 +17,7 @@ obj-$(CONFIG_SENSORS_LM25066) += lm25066.o
>  obj-$(CONFIG_SENSORS_LTC2978)  += ltc2978.o
>  obj-$(CONFIG_SENSORS_LTC3815)  += ltc3815.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
> diff --git a/drivers/hwmon/pmbus/max16601.c b/drivers/hwmon/pmbus/max16601.c
> new file mode 100644
> index 000000000000..e10ab394b8de
> --- /dev/null
> +++ b/drivers/hwmon/pmbus/max16601.c
> @@ -0,0 +1,314 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Hardware monitoring driver for Maxim MAX16601
> + *
> + * Implementation notes:
> + *
> + * Ths chip 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"
> +
> +#define REG_SETPT_DVID         0xd1
> +#define  DAC_10MV_MODE         BIT(4)
> +#define REG_PHASE_ID           0xf3
> +#define  CORE_RAIL_INDICATOR   BIT(7)

Maybe we can move the above two lines down, so the register addresses
become sequential in the source file.

> +#define REG_IOUT_AVG_PK                0xee
> +#define REG_IIN_SENSOR         0xf1
> +#define REG_TOTAL_INPUT_POWER  0xf2
> +#define REG_PHASE_REPORTING    0xf4
> +
> +struct max16601_data {
> +       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)
> +{
> +       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;
> +
> +       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] = 8,
> +       .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 int max16601_probe(struct i2c_client *client,
> +                         const struct i2c_device_id *id)
> +{
> +       struct device *dev = &client->dev;
> +       u8 buf[I2C_SMBUS_BLOCK_MAX + 1];
> +       struct max16601_data *data;
> +       int ret;
> +
> +       if (!i2c_check_functionality(client->adapter,
> +                                    I2C_FUNC_SMBUS_READ_BYTE_DATA |
> +                                    I2C_FUNC_SMBUS_READ_BLOCK_DATA))
> +               return -ENODEV;
> +
> +       ret = i2c_smbus_read_block_data(client, PMBUS_IC_DEVICE_ID, buf);
> +       if (ret < 0)
> +               return -ENODEV;
> +
> +       /* PMBUS_IC_DEVICE_ID is expected to return "MAX16601y.xx" */
> +       if (ret < 11 || strncmp(buf, "MAX16601", 8)) {
> +               buf[ret] = '\0';
> +               dev_err(dev, "Unsupported chip '%s'\n", buf);
> +               return -ENODEV;
> +       }
> +
> +       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->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, id, &data->info);
> +}
> +
> +static const struct i2c_device_id max16601_id[] = {
> +       {"max16601", 0},
> +       {}
> +};
> +
> +MODULE_DEVICE_TABLE(i2c, max16601_id);
> +
> +static struct i2c_driver max16601_driver = {
> +       .driver = {
> +                  .name = "max16601",
> +                  },
> +       .probe = max16601_probe,
> +       .remove = pmbus_do_remove,
> +       .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");
> --
> 2.17.1
>
Guenter Roeck May 8, 2020, 4:29 a.m. UTC | #2
On 5/7/20 5:54 PM, Alex Qiu wrote:
> Hi Guenter,
> 
> The drivers look good on our system, although more tests are ongoing.
> The patch LGTM with minor nit. Thank you very much!
> 
> On Wed, May 6, 2020 at 2:49 PM Guenter Roeck <linux@roeck-us.net> wrote:
>>
>> MAX16601 is a VR13.HC Dual-Output Voltage Regulator Chipset,
>> implementing a (8+1) multiphase synchronous buck converter.
>>
>> Cc: Alex Qiu <xqiu@google.com>
>> Signed-off-by: Guenter Roeck <linux@roeck-us.net>
>> ---

[ ... ]

>> +
>> +#define REG_SETPT_DVID         0xd1
>> +#define  DAC_10MV_MODE         BIT(4)
>> +#define REG_PHASE_ID           0xf3
>> +#define  CORE_RAIL_INDICATOR   BIT(7)
> 
> Maybe we can move the above two lines down, so the register addresses
> become sequential in the source file.
> 

Makes sense. Will do.

Thanks,
Guenter
diff mbox series

Patch

diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
index 8ef62fd39787..df87ae15a1d4 100644
--- a/Documentation/hwmon/index.rst
+++ b/Documentation/hwmon/index.rst
@@ -106,6 +106,7 @@  Hardware Monitoring Kernel Drivers
    max16064
    max16065
    max1619
+   max16601
    max1668
    max197
    max20730
diff --git a/Documentation/hwmon/max16601.rst b/Documentation/hwmon/max16601.rst
new file mode 100644
index 000000000000..346e74674c51
--- /dev/null
+++ b/Documentation/hwmon/max16601.rst
@@ -0,0 +1,159 @@ 
+.. SPDX-License-Identifier: GPL-2.0
+
+Kernel driver max16601
+======================
+
+Supported chips:
+
+  * Maxim MAX16601
+
+    Prefix: 'max16601'
+
+    Addresses scanned: -
+
+    Datasheet: Not published
+
+Author: Guenter Roeck <linux@roeck-us.net>
+
+
+Description
+-----------
+
+This driver supports the MAX16601 VR13.HC Dual-Output Voltage Regulator
+Chipset.
+
+The driver is a client driver to the core PMBus driver.
+Please see Documentation/hwmon/pmbus.rst for details on PMBus client drivers.
+
+
+Usage Notes
+-----------
+
+This driver does not auto-detect devices. You will have to instantiate the
+devices explicitly. Please see Documentation/i2c/instantiating-devices.rst for
+details.
+
+
+Platform data support
+---------------------
+
+The driver supports standard PMBus driver platform data.
+
+
+Sysfs entries
+-------------
+
+The following attributes are supported.
+
+======================= =======================================================
+in1_label		"vin1"
+in1_input		VCORE input voltage.
+in1_alarm		Input voltage alarm.
+
+in2_label		"vout1"
+in2_input		VCORE output voltage.
+in2_alarm		Output voltage alarm.
+
+curr1_label		"iin1"
+curr1_input		VCORE input current, derived from duty cycle and output
+			current.
+curr1_max		Maximum input current.
+curr1_max_alarm		Current high alarm.
+
+curr2_label		"iin1.0"
+curr2_input		VCORE phase 0 input current.
+
+curr3_label		"iin1.1"
+curr3_input		VCORE phase 1 input current.
+
+curr4_label		"iin1.2"
+curr4_input		VCORE phase 2 input current.
+
+curr5_label		"iin1.3"
+curr5_input		VCORE phase 3 input current.
+
+curr6_label		"iin1.4"
+curr6_input		VCORE phase 4 input current.
+
+curr7_label		"iin1.5"
+curr7_input		VCORE phase 5 input current.
+
+curr8_label		"iin1.6"
+curr8_input		VCORE phase 6 input current.
+
+curr9_label		"iin1.7"
+curr9_input		VCORE phase 7 input current.
+
+curr10_label		"iin2"
+curr10_input		VCORE input current, derived from sensor element.
+
+curr11_label		"iin3"
+curr11_input		VSA input current.
+
+curr12_label		"iout1"
+curr12_input		VCORE output current.
+curr12_crit		Critical output current.
+curr12_crit_alarm	Output current critical alarm.
+curr12_max		Maximum output current.
+curr12_max_alarm	Output current high alarm.
+
+curr13_label		"iout1.0"
+curr13_input		VCORE phase 0 output current.
+
+curr14_label		"iout1.1"
+curr14_input		VCORE phase 1 output current.
+
+curr15_label		"iout1.2"
+curr15_input		VCORE phase 2 output current.
+
+curr16_label		"iout1.3"
+curr16_input		VCORE phase 3 output current.
+
+curr17_label		"iout1.4"
+curr17_input		VCORE phase 4 output current.
+
+curr18_label		"iout1.5"
+curr18_input		VCORE phase 5 output current.
+
+curr19_label		"iout1.6"
+curr19_input		VCORE phase 6 output current.
+
+curr20_label		"iout1.7"
+curr20_input		VCORE phase 7 output current.
+
+curr21_label		"iout3"
+curr21_input		VSA output current.
+curr21_highest		Historical maximum VSA output current.
+curr21_reset_history	Write any value to reset curr21_highest.
+curr21_crit		Critical output current.
+curr21_crit_alarm	Output current critical alarm.
+curr21_max		Maximum output current.
+curr21_max_alarm	Output current high alarm.
+
+power1_label		"pin1"
+power1_input		Input power, derived from duty cycle and output current.
+power1_alarm		Input power alarm.
+
+power2_label		"pin2"
+power2_input		Input power, derived from input current sensor.
+
+power3_label		"pout"
+power3_input		Output power.
+
+temp1_input		VCORE temperature.
+temp1_crit		Critical high temperature.
+temp1_crit_alarm	Chip temperature critical high alarm.
+temp1_max		Maximum temperature.
+temp1_max_alarm		Chip temperature high alarm.
+
+temp2_input		TSENSE_0 temperature
+temp3_input		TSENSE_1 temperature
+temp4_input		TSENSE_2 temperature
+temp5_input		TSENSE_3 temperature
+
+temp6_input		VSA temperature.
+temp6_crit		Critical high temperature.
+temp6_crit_alarm	Chip temperature critical high alarm.
+temp6_max		Maximum temperature.
+temp6_max_alarm		Chip temperature high alarm.
+======================= =======================================================
diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig
index de12a565006d..a337195b1c39 100644
--- a/drivers/hwmon/pmbus/Kconfig
+++ b/drivers/hwmon/pmbus/Kconfig
@@ -146,6 +146,15 @@  config SENSORS_MAX16064
 	  This driver can also be built as a module. If so, the module will
 	  be called max16064.
 
+config SENSORS_MAX16601
+	tristate "Maxim MAX16601"
+	help
+	  If you say yes here you get hardware monitoring support for Maxim
+	  MAX16601.
+
+	  This driver can also be built as a module. If so, the module will
+	  be called max16601.
+
 config SENSORS_MAX20730
 	tristate "Maxim MAX20730, MAX20734, MAX20743"
 	help
diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile
index 5feb45806123..c4b15db996ad 100644
--- a/drivers/hwmon/pmbus/Makefile
+++ b/drivers/hwmon/pmbus/Makefile
@@ -17,6 +17,7 @@  obj-$(CONFIG_SENSORS_LM25066)	+= lm25066.o
 obj-$(CONFIG_SENSORS_LTC2978)	+= ltc2978.o
 obj-$(CONFIG_SENSORS_LTC3815)	+= ltc3815.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
diff --git a/drivers/hwmon/pmbus/max16601.c b/drivers/hwmon/pmbus/max16601.c
new file mode 100644
index 000000000000..e10ab394b8de
--- /dev/null
+++ b/drivers/hwmon/pmbus/max16601.c
@@ -0,0 +1,314 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Hardware monitoring driver for Maxim MAX16601
+ *
+ * Implementation notes:
+ *
+ * Ths chip 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"
+
+#define REG_SETPT_DVID		0xd1
+#define  DAC_10MV_MODE		BIT(4)
+#define REG_PHASE_ID		0xf3
+#define  CORE_RAIL_INDICATOR	BIT(7)
+#define REG_IOUT_AVG_PK		0xee
+#define REG_IIN_SENSOR		0xf1
+#define REG_TOTAL_INPUT_POWER	0xf2
+#define REG_PHASE_REPORTING	0xf4
+
+struct max16601_data {
+	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)
+{
+	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;
+
+	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] = 8,
+	.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 int max16601_probe(struct i2c_client *client,
+			  const struct i2c_device_id *id)
+{
+	struct device *dev = &client->dev;
+	u8 buf[I2C_SMBUS_BLOCK_MAX + 1];
+	struct max16601_data *data;
+	int ret;
+
+	if (!i2c_check_functionality(client->adapter,
+				     I2C_FUNC_SMBUS_READ_BYTE_DATA |
+				     I2C_FUNC_SMBUS_READ_BLOCK_DATA))
+		return -ENODEV;
+
+	ret = i2c_smbus_read_block_data(client, PMBUS_IC_DEVICE_ID, buf);
+	if (ret < 0)
+		return -ENODEV;
+
+	/* PMBUS_IC_DEVICE_ID is expected to return "MAX16601y.xx" */
+	if (ret < 11 || strncmp(buf, "MAX16601", 8)) {
+		buf[ret] = '\0';
+		dev_err(dev, "Unsupported chip '%s'\n", buf);
+		return -ENODEV;
+	}
+
+	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->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, id, &data->info);
+}
+
+static const struct i2c_device_id max16601_id[] = {
+	{"max16601", 0},
+	{}
+};
+
+MODULE_DEVICE_TABLE(i2c, max16601_id);
+
+static struct i2c_driver max16601_driver = {
+	.driver = {
+		   .name = "max16601",
+		   },
+	.probe = max16601_probe,
+	.remove = pmbus_do_remove,
+	.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");