root/drivers/hwmon/ltc4283.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Analog Devices LTC4283 I2C Negative Voltage Hot Swap Controller (HWMON)
 *
 * Copyright 2025 Analog Devices Inc.
 */
#include <linux/auxiliary_bus.h>
#include <linux/bitfield.h>
#include <linux/bitmap.h>
#include <linux/bitops.h>
#include <linux/bits.h>

#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/device/devres.h>
#include <linux/hwmon.h>
#include <linux/i2c.h>
#include <linux/math.h>
#include <linux/math64.h>
#include <linux/minmax.h>
#include <linux/module.h>

#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/unaligned.h>
#include <linux/units.h>

#define LTC4283_SYSTEM_STATUS           0x00
#define LTC4283_FAULT_STATUS            0x03
#define   LTC4283_OV_MASK               BIT(0)
#define   LTC4283_UV_MASK               BIT(1)
#define   LTC4283_OC_MASK               BIT(2)
#define   LTC4283_FET_BAD_MASK          BIT(3)
#define   LTC4283_FET_SHORT_MASK        BIT(6)
#define LTC4283_FAULT_LOG               0x04
#define   LTC4283_OV_FAULT_MASK         BIT(0)
#define   LTC4283_UV_FAULT_MASK         BIT(1)
#define   LTC4283_OC_FAULT_MASK         BIT(2)
#define   LTC4283_FET_BAD_FAULT_MASK    BIT(3)
#define   LTC4283_PGI_FAULT_MASK        BIT(4)
#define   LTC4283_PWR_FAIL_FAULT_MASK   BIT(5)
#define   LTC4283_FET_SHORT_FAULT_MASK  BIT(6)
#define LTC4283_ADC_ALM_LOG_1           0x05
#define   LTC4283_POWER_LOW_ALM         BIT(0)
#define   LTC4283_POWER_HIGH_ALM        BIT(1)
#define   LTC4283_SENSE_LOW_ALM         BIT(4)
#define   LTC4283_SENSE_HIGH_ALM        BIT(5)
#define LTC4283_ADC_ALM_LOG_2           0x06
#define LTC4283_ADC_ALM_LOG_3           0x07
#define LTC4283_ADC_ALM_LOG_4           0x08
#define LTC4283_ADC_ALM_LOG_5           0x09
#define LTC4283_CONTROL_1               0x0a
#define   LTC4283_RW_PAGE_MASK          BIT(0)
#define   LTC4283_PIGIO2_ACLB_MASK      BIT(2)
#define   LTC4283_PWRGD_RST_CTRL_MASK   BIT(3)
#define   LTC4283_FET_BAD_OFF_MASK      BIT(4)
#define   LTC4283_THERM_TMR_MASK        BIT(5)
#define   LTC4283_DVDT_MASK             BIT(6)
#define LTC4283_CONTROL_2               0x0b
#define   LTC4283_OV_RETRY_MASK         BIT(0)
#define   LTC4283_UV_RETRY_MASK         BIT(1)
#define   LTC4283_OC_RETRY_MASK         GENMASK(3, 2)
#define   LTC4283_FET_BAD_RETRY_MASK    GENMASK(5, 4)
#define   LTC4283_EXT_FAULT_RETRY_MASK  BIT(7)
#define LTC4283_RESERVED_OC             0x0c
#define LTC4283_CONFIG_1                0x0d
#define   LTC4283_FB_MASK               GENMASK(3, 2)
#define   LTC4283_ILIM_MASK             GENMASK(7, 4)
#define LTC4283_CONFIG_2                0x0e
#define   LTC4283_COOLING_DL_MASK       GENMASK(3, 1)
#define   LTC4283_FTBD_DL_MASK          GENMASK(5, 4)
#define LTC4283_CONFIG_3                0x0f
#define   LTC4283_VPWR_DRNS_MASK        BIT(6)
#define   LTC4283_EXTFLT_TURN_OFF_MASK  BIT(7)
#define LTC4283_PGIO_CONFIG             0x10
#define   LTC4283_PGIO1_CFG_MASK        GENMASK(1, 0)
#define   LTC4283_PGIO2_CFG_MASK        GENMASK(3, 2)
#define   LTC4283_PGIO3_CFG_MASK        GENMASK(5, 4)
#define   LTC4283_PGIO4_CFG_MASK        GENMASK(7, 6)
#define LTC4283_PGIO_CONFIG_2           0x11
#define   LTC4283_ADC_MASK              GENMASK(2, 0)
#define LTC4283_ADC_SELECT(c)           (0x13 + (c) / 8)
#define   LTC4283_ADC_SELECT_MASK(c)    BIT((c) % 8)
#define LTC4283_SENSE_MIN_TH            0x1b
#define LTC4283_SENSE_MAX_TH            0x1c
#define LTC4283_VPWR_MIN_TH             0x1d
#define LTC4283_VPWR_MAX_TH             0x1e
#define LTC4283_POWER_MIN_TH            0x1f
#define LTC4283_POWER_MAX_TH            0x20
#define LTC4283_ADC_2_MIN_TH(c)         (0x21 + (c) * 2)
#define LTC4283_ADC_2_MAX_TH(c)         (0x22 + (c) * 2)
#define LTC4283_ADC_2_MIN_TH_DIFF(c)    (0x39 + (c) * 2)
#define LTC4283_ADC_2_MAX_TH_DIFF(c)    (0x3a + (c) * 2)
#define LTC4283_SENSE                   0x41
#define LTC4283_SENSE_MIN               0x42
#define LTC4283_SENSE_MAX               0x43
#define LTC4283_VPWR                    0x44
#define LTC4283_VPWR_MIN                0x45
#define LTC4283_VPWR_MAX                0x46
#define LTC4283_POWER                   0x47
#define LTC4283_POWER_MIN               0x48
#define LTC4283_POWER_MAX               0x49
#define LTC4283_RESERVED_68             0x68
#define LTC4283_RESERVED_6D             0x6D
/* get channels from ADC 2 */
#define LTC4283_ADC_2(c)                (0x4a + (c) * 3)
#define LTC4283_ADC_2_MIN(c)            (0x4b + (c) * 3)
#define LTC4283_ADC_2_MAX(c)            (0x4c + (c) * 3)
#define LTC4283_ADC_2_DIFF(c)           (0x6e + (c) * 3)
#define LTC4283_ADC_2_MIN_DIFF(c)       (0x6f + (c) * 3)
#define LTC4283_ADC_2_MAX_DIFF(c)       (0x70 + (c) * 3)
#define LTC4283_ENERGY                  0x7a
#define LTC4283_METER_CONTROL           0x84
#define   LTC4283_INTEGRATE_I_MASK      BIT(0)
#define   LTC4283_METER_HALT_MASK       BIT(6)
#define LTC4283_RESERVED_86             0x86
#define LTC4283_RESERVED_8F             0x8F
#define LTC4283_FAULT_LOG_CTRL          0x90
#define   LTC4283_FAULT_LOG_EN_MASK     BIT(7)
#define LTC4283_RESERVED_91             0x91
#define LTC4283_RESERVED_A1             0xA1
#define LTC4283_RESERVED_A3             0xA3
#define LTC4283_RESERVED_AC             0xAC
#define LTC4283_POWER_PLAY_MSB          0xE7
#define LTC4283_POWER_PLAY_LSB          0xE8
#define LTC4283_RESERVED_F1             0xF1
#define LTC4283_RESERVED_FF             0xFF

/* also applies for differential channels */
#define LTC4283_ADC1_FS_uV              32768
#define LTC4283_ADC2_FS_mV              2048
#define LTC4283_TCONV_uS                64103
#define LTC4283_VILIM_MIN_uV            15000
#define LTC4283_VILIM_MAX_uV            30000
#define LTC4283_VILIM_RANGE     \
        (LTC4283_VILIM_MAX_uV - LTC4283_VILIM_MIN_uV + 1)

#define LTC4283_PGIO_FUNC_GPIO          2
#define LTC4283_PGIO2_FUNC_ACLB         3

/*
 * Maximum value for rsense in nano ohms. The reasoning for this value is that
 * it's the max value for which multiplying by 256 does not overflow long on
 * 32bits. For the minimum value, is a sane minimum rsense for which power_max
 * does not overflow 32bits.
 */
#define LTC4283_MAX_RSENSE      1677721599
#define LTC4283_MIN_RSENSE      50000

/* voltage channels */
enum {
        LTC4283_CHAN_VIN,
        LTC4283_CHAN_VPWR,
        LTC4283_CHAN_ADI_1,
        LTC4283_CHAN_ADI_2,
        LTC4283_CHAN_ADI_3,
        LTC4283_CHAN_ADI_4,
        LTC4283_CHAN_ADIO_1,
        LTC4283_CHAN_ADIO_2,
        LTC4283_CHAN_ADIO_3,
        LTC4283_CHAN_ADIO_4,
        LTC4283_CHAN_DRNS,
        LTC4283_CHAN_DRAIN,
        /* differential channels */
        LTC4283_CHAN_ADIN12,
        LTC4283_CHAN_ADIN34,
        LTC4283_CHAN_ADIO12,
        LTC4283_CHAN_ADIO34,
        LTC4283_CHAN_MAX
};

/* Just for ease of use on the regmap  */
#define LTC4283_ADIO34_MAX \
        LTC4283_ADC_2_MAX_DIFF(LTC4283_CHAN_ADIO34 - LTC4283_CHAN_ADIN12)

struct ltc4283_hwmon {
        struct regmap *map;
        struct i2c_client *client;
        unsigned long gpio_mask;
        unsigned long ch_enable_mask;
        /* in microwatt */
        unsigned long power_max;
        /* in millivolt */
        u32 vsense_max;
        /* in tenths of microohm*/
        u32 rsense;
        bool energy_en;
        bool ext_fault;
};

static int ltc4283_read_voltage_word(const struct ltc4283_hwmon *st,
                                     u32 reg, u32 fs, long *val)
{
        unsigned int __raw;
        int ret;

        ret = regmap_read(st->map, reg, &__raw);
        if (ret)
                return ret;

        *val = DIV_ROUND_CLOSEST(__raw * fs, BIT(16));
        return 0;
}

static int ltc4283_read_voltage_byte(const struct ltc4283_hwmon *st,
                                     u32 reg, u32 fs, long *val)
{
        int ret;
        u32 in;

        ret = regmap_read(st->map, reg, &in);
        if (ret)
                return ret;

        *val = DIV_ROUND_CLOSEST(in * fs, BIT(8));
        return 0;
}

static u32 ltc4283_in_reg(u32 attr, u32 channel)
{
        switch (attr) {
        case hwmon_in_input:
                if (channel == LTC4283_CHAN_VPWR)
                        return LTC4283_VPWR;
                if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_DRAIN)
                        return LTC4283_ADC_2(channel - LTC4283_CHAN_ADI_1);
                return LTC4283_ADC_2_DIFF(channel - LTC4283_CHAN_ADIN12);
        case hwmon_in_highest:
                if (channel == LTC4283_CHAN_VPWR)
                        return LTC4283_VPWR_MAX;
                if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_DRAIN)
                        return LTC4283_ADC_2_MAX(channel - LTC4283_CHAN_ADI_1);
                return LTC4283_ADC_2_MAX_DIFF(channel - LTC4283_CHAN_ADIN12);
        case hwmon_in_lowest:
                if (channel == LTC4283_CHAN_VPWR)
                        return LTC4283_VPWR_MIN;
                if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_DRAIN)
                        return LTC4283_ADC_2_MIN(channel - LTC4283_CHAN_ADI_1);
                return LTC4283_ADC_2_MIN_DIFF(channel - LTC4283_CHAN_ADIN12);
        case hwmon_in_max:
                if (channel == LTC4283_CHAN_VPWR)
                        return LTC4283_VPWR_MAX_TH;
                if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_DRAIN)
                        return LTC4283_ADC_2_MAX_TH(channel - LTC4283_CHAN_ADI_1);
                return LTC4283_ADC_2_MAX_TH_DIFF(channel - LTC4283_CHAN_ADIN12);
        default:
                if (channel == LTC4283_CHAN_VPWR)
                        return LTC4283_VPWR_MIN_TH;
                if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_DRAIN)
                        return LTC4283_ADC_2_MIN_TH(channel - LTC4283_CHAN_ADI_1);
                return LTC4283_ADC_2_MIN_TH_DIFF(channel - LTC4283_CHAN_ADIN12);
        }
}

static int ltc4283_read_in_vals(const struct ltc4283_hwmon *st,
                                u32 attr, u32 channel, long *val)
{
        u32 reg = ltc4283_in_reg(attr, channel);
        int ret;

        if (channel < LTC4283_CHAN_ADIN12) {
                if (attr != hwmon_in_max && attr != hwmon_in_min)
                        return ltc4283_read_voltage_word(st, reg,
                                                         LTC4283_ADC2_FS_mV,
                                                         val);

                return ltc4283_read_voltage_byte(st, reg,
                                                 LTC4283_ADC2_FS_mV, val);
        }

        if (attr != hwmon_in_max && attr != hwmon_in_min)
                ret = ltc4283_read_voltage_word(st, reg,
                                                LTC4283_ADC1_FS_uV, val);
        else
                ret = ltc4283_read_voltage_byte(st, reg,
                                                LTC4283_ADC1_FS_uV, val);
        if (ret)
                return ret;

        *val = DIV_ROUND_CLOSEST(*val, MILLI);
        return 0;
}

static int ltc4283_read_alarm(struct ltc4283_hwmon *st, u32 reg,
                              u32 mask, long *val)
{
        u32 alarm;
        int ret;

        ret = regmap_read(st->map, reg, &alarm);
        if (ret)
                return ret;

        *val = !!(alarm & mask);

        /* If not status/fault logs, clear the alarm after reading it. */
        if (reg != LTC4283_FAULT_STATUS && reg != LTC4283_FAULT_LOG)
                return regmap_write(st->map, reg, alarm & ~mask);

        return 0;
}

static int ltc4283_read_in_alarm(struct ltc4283_hwmon *st, u32 channel,
                                 bool max_alm, long *val)
{
        if (channel == LTC4283_CHAN_VPWR)
                return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_1,
                                          BIT(2 + max_alm), val);

        if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_ADI_4) {
                u32 bit = (channel - LTC4283_CHAN_ADI_1) * 2;
                /*
                 * Lower channels go to higher bits. We also want to go +1 down
                 * in the min_alarm case.
                 */
                return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_2,
                                          BIT(7 - bit - !max_alm), val);
        }

        if (channel >= LTC4283_CHAN_ADIO_1 && channel <= LTC4283_CHAN_ADIO_4) {
                u32 bit = (channel - LTC4283_CHAN_ADIO_1) * 2;

                return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_3,
                                          BIT(7 - bit - !max_alm), val);
        }

        if (channel >= LTC4283_CHAN_ADIN12 && channel <= LTC4283_CHAN_ADIO34) {
                u32 bit = (channel - LTC4283_CHAN_ADIN12) * 2;

                return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_5,
                                          BIT(7 - bit - !max_alm), val);
        }

        if (channel == LTC4283_CHAN_DRNS)
                return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_4,
                                          BIT(6 + max_alm), val);

        return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_4, BIT(4 + max_alm),
                                  val);
}

static int ltc4283_read_in(struct ltc4283_hwmon *st, u32 attr, u32 channel,
                           long *val)
{
        switch (attr) {
        case hwmon_in_input:
                if (!test_bit(channel, &st->ch_enable_mask))
                        return -ENODATA;

                return ltc4283_read_in_vals(st, attr, channel, val);
        case hwmon_in_highest:
        case hwmon_in_lowest:
        case hwmon_in_max:
        case hwmon_in_min:
                return ltc4283_read_in_vals(st, attr, channel, val);
        case hwmon_in_max_alarm:
                return ltc4283_read_in_alarm(st, channel, true, val);
        case hwmon_in_min_alarm:
                return ltc4283_read_in_alarm(st, channel, false, val);
        case hwmon_in_crit_alarm:
                return ltc4283_read_alarm(st, LTC4283_FAULT_STATUS,
                                          LTC4283_OV_MASK, val);
        case hwmon_in_lcrit_alarm:
                return ltc4283_read_alarm(st, LTC4283_FAULT_STATUS,
                                          LTC4283_UV_MASK, val);
        case hwmon_in_fault:
                /*
                 * We report failure if we detect either a fer_bad or a
                 * fet_short in the status register.
                 */
                return ltc4283_read_alarm(st, LTC4283_FAULT_STATUS,
                                          LTC4283_FET_BAD_MASK | LTC4283_FET_SHORT_MASK, val);
        case hwmon_in_enable:
                *val = test_bit(channel, &st->ch_enable_mask);
                return 0;
        default:
                return -EOPNOTSUPP;
        }
        return 0;
}

static int ltc4283_read_current_word(const struct ltc4283_hwmon *st, u32 reg,
                                     long *val)
{
        u64 temp = (u64)LTC4283_ADC1_FS_uV * DECA * MILLI;
        unsigned int __raw;
        int ret;

        ret = regmap_read(st->map, reg, &__raw);
        if (ret)
                return ret;

        *val = DIV64_U64_ROUND_CLOSEST(__raw * temp,
                                       BIT_ULL(16) * st->rsense);

        return 0;
}

static int ltc4283_read_current_byte(const struct ltc4283_hwmon *st, u32 reg,
                                     long *val)
{
        u64 temp = (u64)LTC4283_ADC1_FS_uV * DECA * MILLI;
        u32 curr;
        int ret;

        ret = regmap_read(st->map, reg, &curr);
        if (ret)
                return ret;

        *val = DIV_ROUND_CLOSEST_ULL(curr * temp, BIT(8) * st->rsense);
        return 0;
}

static int ltc4283_read_curr(struct ltc4283_hwmon *st, u32 attr, long *val)
{
        switch (attr) {
        case hwmon_curr_input:
                return ltc4283_read_current_word(st, LTC4283_SENSE, val);
        case hwmon_curr_highest:
                return ltc4283_read_current_word(st, LTC4283_SENSE_MAX, val);
        case hwmon_curr_lowest:
                return ltc4283_read_current_word(st, LTC4283_SENSE_MIN, val);
        case hwmon_curr_max:
                return ltc4283_read_current_byte(st, LTC4283_SENSE_MAX_TH, val);
        case hwmon_curr_min:
                return ltc4283_read_current_byte(st, LTC4283_SENSE_MIN_TH, val);
        case hwmon_curr_max_alarm:
                return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_1,
                                          LTC4283_SENSE_HIGH_ALM, val);
        case hwmon_curr_min_alarm:
                return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_1,
                                          LTC4283_SENSE_LOW_ALM, val);
        case hwmon_curr_crit_alarm:
                return ltc4283_read_alarm(st, LTC4283_FAULT_STATUS,
                                          LTC4283_OC_MASK, val);
        default:
                return -EOPNOTSUPP;
        }
}

static int ltc4283_read_power_word(const struct ltc4283_hwmon *st,
                                   u32 reg, long *val)
{
        u64 temp = (u64)LTC4283_ADC1_FS_uV * LTC4283_ADC2_FS_mV * DECA * MILLI;
        unsigned int __raw;
        int ret;

        ret = regmap_read(st->map, reg, &__raw);
        if (ret)
                return ret;

        /*
         * Power is given by:
         *     P = CODE(16b) * 32.768mV * 2.048V / (2^16 * Rsense)
         */
        *val = DIV64_U64_ROUND_CLOSEST(temp * __raw, BIT_ULL(16) * st->rsense);

        return 0;
}

static int ltc4283_read_power_byte(const struct ltc4283_hwmon *st,
                                   u32 reg, long *val)
{
        u64 temp = (u64)LTC4283_ADC1_FS_uV * LTC4283_ADC2_FS_mV * DECA * MILLI;
        u32 power;
        int ret;

        ret = regmap_read(st->map, reg, &power);
        if (ret)
                return ret;

        *val = DIV_ROUND_CLOSEST_ULL(power * temp, BIT(8) * st->rsense);

        return 0;
}

static int ltc4283_read_power(struct ltc4283_hwmon *st, u32 attr, long *val)
{
        switch (attr) {
        case hwmon_power_input:
                return ltc4283_read_power_word(st, LTC4283_POWER, val);
        case hwmon_power_input_highest:
                return ltc4283_read_power_word(st, LTC4283_POWER_MAX, val);
        case hwmon_power_input_lowest:
                return ltc4283_read_power_word(st, LTC4283_POWER_MIN, val);
        case hwmon_power_max_alarm:
                return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_1,
                                          LTC4283_POWER_HIGH_ALM, val);
        case hwmon_power_min_alarm:
                return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_1,
                                          LTC4283_POWER_LOW_ALM, val);
        case hwmon_power_max:
                return ltc4283_read_power_byte(st, LTC4283_POWER_MAX_TH, val);
        case hwmon_power_min:
                return ltc4283_read_power_byte(st, LTC4283_POWER_MIN_TH, val);
        default:
                return -EOPNOTSUPP;
        }
}

static int ltc4283_read_energy(struct ltc4283_hwmon *st, u32 attr, s64 *val)
{
        u64 temp = LTC4283_ADC1_FS_uV * LTC4283_ADC2_FS_mV, energy;
        u8 raw[8] = {};
        int ret;

        if (!st->energy_en)
                return -ENODATA;

        ret = i2c_smbus_read_i2c_block_data(st->client, LTC4283_ENERGY, 6, raw);
        if (ret < 0)
                return ret;
        if (ret != 6)
                return -EIO;

        energy = get_unaligned_be64(raw) >> 16;

        /*
         * The formula for energy is given by:
         *      E = CODE(48b) * 32.768mV * 2.048V * Tconv / 2^24 * Rsense
         *
         * As Rsense can have tenths of micro-ohm resolution, we need to
         * multiply by DECA to get microjoule.
         */

        /*
         * Use mul_u64_u64_div_u64() to handle the 128-bit intermediate
         * product of energy (up to 48 bits) * temp * Tconv without overflow.
         * Multiply rsense by CENTI to convert from tenths-of-microohm back
         * to nanoohm so the result comes out in microjoule.
         */
        energy = mul_u64_u64_div_u64(energy, temp * LTC4283_TCONV_uS,
                                     BIT_ULL(24) * st->rsense * CENTI);

        *val = energy;
        return 0;
}

static int ltc4283_read(struct device *dev, enum hwmon_sensor_types type,
                        u32 attr, int channel, long *val)
{
        struct ltc4283_hwmon *st = dev_get_drvdata(dev);

        switch (type) {
        case hwmon_in:
                return ltc4283_read_in(st, attr, channel, val);
        case hwmon_curr:
                return ltc4283_read_curr(st, attr, val);
        case hwmon_power:
                return ltc4283_read_power(st, attr, val);
        case hwmon_energy:
                *val = st->energy_en;
                return 0;
        case hwmon_energy64:
                return ltc4283_read_energy(st, attr, (s64 *)val);
        default:
                return -EOPNOTSUPP;
        }
}

static int ltc4283_write_power_byte(const struct ltc4283_hwmon *st, u32 reg,
                                    long val)
{
        u64 temp = (u64)LTC4283_ADC1_FS_uV * LTC4283_ADC2_FS_mV * DECA * MILLI;
        u32 __raw;

        val = clamp_val(val, 0, st->power_max);
        __raw = DIV64_U64_ROUND_CLOSEST(val * BIT_ULL(8) * st->rsense, temp);

        return regmap_write(st->map, reg, __raw);
}

static int ltc4283_write_power_word(const struct ltc4283_hwmon *st,
                                    u32 reg, unsigned long val)
{
        u64 divisor = (u64)LTC4283_ADC1_FS_uV * LTC4283_ADC2_FS_mV * DECA * MILLI;
        u16 __raw;

        __raw = mul_u64_u64_div_u64(val, st->rsense * BIT_ULL(16), divisor);

        return regmap_write(st->map, reg, __raw);
}

static int ltc4283_reset_power_hist(struct ltc4283_hwmon *st)
{
        int ret;

        ret = ltc4283_write_power_word(st, LTC4283_POWER_MIN, st->power_max);
        if (ret)
                return ret;

        ret = ltc4283_write_power_word(st, LTC4283_POWER_MAX, 0);
        if (ret)
                return ret;

        /* Clear possible power faults. */
        return regmap_clear_bits(st->map, LTC4283_FAULT_LOG,
                                 LTC4283_PWR_FAIL_FAULT_MASK | LTC4283_PGI_FAULT_MASK);
}

static int ltc4283_write_power(struct ltc4283_hwmon *st, u32 attr, long val)
{
        switch (attr) {
        case hwmon_power_max:
                return ltc4283_write_power_byte(st, LTC4283_POWER_MAX_TH, val);
        case hwmon_power_min:
                return ltc4283_write_power_byte(st, LTC4283_POWER_MIN_TH, val);
        case hwmon_power_reset_history:
                return ltc4283_reset_power_hist(st);
        default:
                return -EOPNOTSUPP;
        }
}

static int ltc4283_write_in_history(struct ltc4283_hwmon *st, u32 reg,
                                    long lowest, u32 fs)
{
        u32 __raw;
        int ret;

        __raw = DIV_ROUND_CLOSEST(BIT(16) * lowest, fs);
        if (__raw == BIT(16))
                __raw = U16_MAX;

        ret = regmap_write(st->map, reg, __raw);
        if (ret)
                return ret;

        return regmap_write(st->map, reg + 1, 0);
}

static int ltc4283_write_in_byte(const struct ltc4283_hwmon *st,
                                 u32 reg, u32 fs, long val)
{
        u32 __raw;

        val = clamp_val(val, 0, fs);
        __raw = DIV_ROUND_CLOSEST(val * BIT(8), fs);
        if (__raw == BIT(8))
                __raw = U8_MAX;

        return regmap_write(st->map, reg, __raw);
}

static int ltc4283_reset_in_hist(struct ltc4283_hwmon *st, u32 channel)
{
        u32 reg, fs;
        int ret;

        /*
         * Make sure to clear possible under/over voltage faults. Otherwise the
         * chip won't latch on again.
         */
        if (channel == LTC4283_CHAN_VIN)
                return regmap_clear_bits(st->map, LTC4283_FAULT_LOG,
                                         LTC4283_OV_FAULT_MASK | LTC4283_UV_FAULT_MASK);

        if (channel == LTC4283_CHAN_VPWR)
                return ltc4283_write_in_history(st, LTC4283_VPWR_MIN,
                                                LTC4283_ADC2_FS_mV,
                                                LTC4283_ADC2_FS_mV);

        if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_DRAIN) {
                fs = LTC4283_ADC2_FS_mV;
                reg = LTC4283_ADC_2_MIN(channel - LTC4283_CHAN_ADI_1);
        } else {
                fs = LTC4283_ADC1_FS_uV;
                reg = LTC4283_ADC_2_MIN_DIFF(channel - LTC4283_CHAN_ADIN12);
        }

        ret = ltc4283_write_in_history(st, reg, fs, fs);
        if (ret)
                return ret;
        if (channel != LTC4283_CHAN_DRAIN)
                return 0;

        /* Then, let's also clear possible fet faults. Same as above. */
        return regmap_clear_bits(st->map, LTC4283_FAULT_LOG,
                                 LTC4283_FET_BAD_FAULT_MASK | LTC4283_FET_SHORT_FAULT_MASK);
}

static int ltc4283_write_in_en(struct ltc4283_hwmon *st, u32 channel, bool en)
{
        unsigned int bit, adc_idx = channel - LTC4283_CHAN_ADI_1;
        unsigned int reg = LTC4283_ADC_SELECT(adc_idx);
        int ret;

        bit = LTC4283_ADC_SELECT_MASK(adc_idx);
        if (channel > LTC4283_CHAN_DRAIN)
                /* Account for two reserved fields after DRAIN. */
                bit <<= 2;

        if (en)
                ret = regmap_set_bits(st->map, reg, bit);
        else
                ret = regmap_clear_bits(st->map, reg, bit);
        if (ret)
                return ret;

        __assign_bit(channel, &st->ch_enable_mask, en);
        return 0;
}

static int ltc4283_write_minmax(struct ltc4283_hwmon *st, long val,
                                u32 channel, bool is_max)
{
        u32 reg;

        if (channel == LTC4283_CHAN_VPWR) {
                if (is_max)
                        return ltc4283_write_in_byte(st, LTC4283_VPWR_MAX_TH,
                                                     LTC4283_ADC2_FS_mV, val);

                return ltc4283_write_in_byte(st, LTC4283_VPWR_MIN_TH,
                                             LTC4283_ADC2_FS_mV, val);
        }

        if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_DRAIN) {
                if (is_max) {
                        reg = LTC4283_ADC_2_MAX_TH(channel - LTC4283_CHAN_ADI_1);
                        return ltc4283_write_in_byte(st, reg,
                                                     LTC4283_ADC2_FS_mV, val);
                }

                reg = LTC4283_ADC_2_MIN_TH(channel - LTC4283_CHAN_ADI_1);
                return ltc4283_write_in_byte(st, reg, LTC4283_ADC2_FS_mV, val);
        }

        /* Clamp before multiplying to avoid overflow on any arch. */
        val = clamp_val(val, 0, LONG_MAX / MILLI);

        if (is_max) {
                reg = LTC4283_ADC_2_MAX_TH_DIFF(channel - LTC4283_CHAN_ADIN12);
                return ltc4283_write_in_byte(st, reg, LTC4283_ADC1_FS_uV,
                                             val * MILLI);
        }

        reg = LTC4283_ADC_2_MIN_TH_DIFF(channel - LTC4283_CHAN_ADIN12);
        return ltc4283_write_in_byte(st, reg, LTC4283_ADC1_FS_uV, val * MILLI);
}

static int ltc4283_write_in(struct ltc4283_hwmon *st, u32 attr, long val,
                            int channel)
{
        switch (attr) {
        case hwmon_in_max:
                return ltc4283_write_minmax(st, val, channel, true);
        case hwmon_in_min:
                return ltc4283_write_minmax(st, val, channel, false);
        case hwmon_in_reset_history:
                return ltc4283_reset_in_hist(st, channel);
        case hwmon_in_enable:
                return ltc4283_write_in_en(st, channel, !!val);
        default:
                return -EOPNOTSUPP;
        }
}

static int ltc4283_write_curr_byte(const struct ltc4283_hwmon *st,
                                   u32 reg, long val)
{
        u32 temp = LTC4283_ADC1_FS_uV * DECA * MILLI;
        u32 reg_val, isense_max;

        isense_max = DIV_ROUND_CLOSEST(st->vsense_max * MICRO * DECA, st->rsense);
        val = clamp_val(val, 0, isense_max);
        reg_val = DIV_ROUND_CLOSEST_ULL(val * BIT_ULL(8) * st->rsense, temp);

        return regmap_write(st->map, reg, reg_val);
}

static int ltc4283_write_curr_history(struct ltc4283_hwmon *st)
{
        int ret;

        ret = ltc4283_write_in_history(st, LTC4283_SENSE_MIN,
                                       st->vsense_max * MILLI,
                                       LTC4283_ADC1_FS_uV);
        if (ret)
                return ret;

        /* Now, let's also clear possible overcurrent logs. */
        return regmap_clear_bits(st->map, LTC4283_FAULT_LOG,
                                 LTC4283_OC_FAULT_MASK);
}

static int ltc4283_write_curr(struct ltc4283_hwmon *st, u32 attr, long val)
{
        switch (attr) {
        case hwmon_curr_max:
                return ltc4283_write_curr_byte(st, LTC4283_SENSE_MAX_TH, val);
        case hwmon_curr_min:
                return ltc4283_write_curr_byte(st, LTC4283_SENSE_MIN_TH, val);
        case hwmon_curr_reset_history:
                return ltc4283_write_curr_history(st);
        default:
                return -EOPNOTSUPP;
        }
}

static int ltc4283_energy_enable_set(struct ltc4283_hwmon *st, long val)
{
        int ret;

        /* Setting the bit halts the meter. */
        val = !!val;
        ret = regmap_update_bits(st->map, LTC4283_METER_CONTROL,
                                 LTC4283_METER_HALT_MASK,
                                 FIELD_PREP(LTC4283_METER_HALT_MASK, !val));
        if (ret)
                return ret;

        st->energy_en = val;

        return 0;
}

static int ltc4283_write(struct device *dev, enum hwmon_sensor_types type,
                         u32 attr, int channel, long val)
{
        struct ltc4283_hwmon *st = dev_get_drvdata(dev);

        switch (type) {
        case hwmon_power:
                return ltc4283_write_power(st, attr, val);
        case hwmon_in:
                return ltc4283_write_in(st, attr, val, channel);
        case hwmon_curr:
                return ltc4283_write_curr(st, attr, val);
        case hwmon_energy:
                return ltc4283_energy_enable_set(st, val);
        default:
                return -EOPNOTSUPP;
        }
}

static umode_t ltc4283_in_is_visible(const struct ltc4283_hwmon *st,
                                     u32 attr, int channel)
{
        /* If ADIO is set as a GPIO, don´t make it visible. */
        if (channel >= LTC4283_CHAN_ADIO_1 && channel <= LTC4283_CHAN_ADIO_4) {
                /* ADIOX pins come at index 0 in the gpio mask. */
                channel -= LTC4283_CHAN_ADIO_1;
                if (test_bit(channel, &st->gpio_mask))
                        return 0;
        }

        /* Also take care of differential channels. */
        if (channel >= LTC4283_CHAN_ADIO12 && channel <= LTC4283_CHAN_ADIO34) {
                channel -= LTC4283_CHAN_ADIO12;
                /* If one channel in the pair is used, make it invisible. */
                if (test_bit(channel * 2, &st->gpio_mask) ||
                    test_bit(channel * 2 + 1, &st->gpio_mask))
                        return 0;
        }

        switch (attr) {
        case hwmon_in_input:
        case hwmon_in_highest:
        case hwmon_in_lowest:
        case hwmon_in_max_alarm:
        case hwmon_in_min_alarm:
        case hwmon_in_label:
        case hwmon_in_lcrit_alarm:
        case hwmon_in_crit_alarm:
        case hwmon_in_fault:
                return 0444;
        case hwmon_in_max:
        case hwmon_in_min:
        case hwmon_in_enable:
                return 0644;
        case hwmon_in_reset_history:
                return 0200;
        default:
                return 0;
        }
}

static umode_t ltc4283_curr_is_visible(u32 attr)
{
        switch (attr) {
        case hwmon_curr_input:
        case hwmon_curr_highest:
        case hwmon_curr_lowest:
        case hwmon_curr_max_alarm:
        case hwmon_curr_min_alarm:
        case hwmon_curr_crit_alarm:
        case hwmon_curr_label:
                return 0444;
        case hwmon_curr_max:
        case hwmon_curr_min:
                return 0644;
        case hwmon_curr_reset_history:
                return 0200;
        default:
                return 0;
        }
}

static umode_t ltc4283_power_is_visible(u32 attr)
{
        switch (attr) {
        case hwmon_power_input:
        case hwmon_power_input_highest:
        case hwmon_power_input_lowest:
        case hwmon_power_label:
        case hwmon_power_max_alarm:
        case hwmon_power_min_alarm:
                return 0444;
        case hwmon_power_max:
        case hwmon_power_min:
                return 0644;
        case hwmon_power_reset_history:
                return 0200;
        default:
                return 0;
        }
}

static umode_t ltc4283_is_visible(const void *data,
                                  enum hwmon_sensor_types type,
                                  u32 attr, int channel)
{
        switch (type) {
        case hwmon_in:
                return ltc4283_in_is_visible(data, attr, channel);
        case hwmon_curr:
                return ltc4283_curr_is_visible(attr);
        case hwmon_power:
                return ltc4283_power_is_visible(attr);
        case hwmon_energy:
                /* hwmon_energy_enable */
                return 0644;
        case hwmon_energy64:
                /* hwmon_energy_input */
                return 0444;
        default:
                return 0;
        }
}

static const char * const ltc4283_in_strs[] = {
        "VIN", "VPWR", "VADI1", "VADI2", "VADI3", "VADI4", "VADIO1", "VADIO2",
        "VADIO3", "VADIO4", "DRNS", "DRAIN", "ADIN2-ADIN1", "ADIN4-ADIN3",
        "ADIO2-ADIO1", "ADIO4-ADIO3"
};

static int ltc4283_read_labels(struct device *dev,
                               enum hwmon_sensor_types type,
                               u32 attr, int channel, const char **str)
{
        switch (type) {
        case hwmon_in:
                *str = ltc4283_in_strs[channel];
                return 0;
        case hwmon_curr:
                *str = "ISENSE";
                return 0;
        case hwmon_power:
                *str = "Power";
                return 0;
        default:
                return -EOPNOTSUPP;
        }
}

/*
 * Set max limits for ISENSE and Power as that depends on the max voltage on
 * rsense that is defined in ILIM_ADJUST. This is specially important for power
 * because for some rsense and vfsout values, if we allow the default raw 255
 * value, that would overflow long in 32bit archs when reading back the max
 * power limit.
 */
static int ltc4283_set_max_limits(struct ltc4283_hwmon *st, struct device *dev)
{
        u32 temp = st->vsense_max * DECA * MICRO;
        int ret;

        ret = ltc4283_write_in_byte(st, LTC4283_SENSE_MAX_TH, LTC4283_ADC1_FS_uV,
                                    st->vsense_max * MILLI);
        if (ret)
                return ret;

        /* Power is given by ISENSE * Vout. */
        st->power_max = DIV_ROUND_CLOSEST(temp, st->rsense) * LTC4283_ADC2_FS_mV;
        return ltc4283_write_power_byte(st, LTC4283_POWER_MAX_TH, st->power_max);
}

static int ltc4283_parse_array_prop(const struct ltc4283_hwmon *st,
                                    struct device *dev, const char *prop,
                                    const u32 *vals, u32 n_vals)
{
        u32 prop_val;
        int ret;
        u32 i;

        ret = device_property_read_u32(dev, prop, &prop_val);
        if (ret)
                return n_vals;

        for (i = 0; i < n_vals; i++) {
                if (prop_val != vals[i])
                        continue;

                return i;
        }

        return dev_err_probe(dev, -EINVAL,
                             "Invalid %s property value %u\n", prop, prop_val);
}

static int ltc4283_get_defaults(struct ltc4283_hwmon *st)
{
        u32 reg_val, ilm_adjust, c;
        int ret;

        ret = regmap_read(st->map, LTC4283_METER_CONTROL, &reg_val);
        if (ret)
                return ret;

        st->energy_en = !FIELD_GET(LTC4283_METER_HALT_MASK, reg_val);

        ret = regmap_read(st->map, LTC4283_CONFIG_1, &reg_val);
        if (ret)
                return ret;

        ilm_adjust = FIELD_GET(LTC4283_ILIM_MASK, reg_val);
        st->vsense_max = LTC4283_VILIM_MIN_uV / MILLI + ilm_adjust;

        ret = regmap_read(st->map, LTC4283_PGIO_CONFIG, &reg_val);
        if (ret)
                return ret;

        /* Can be latter overwritten in ltc4283_pgio_config() */
        if (FIELD_GET(LTC4283_PGIO4_CFG_MASK, reg_val) < LTC4283_PGIO_FUNC_GPIO)
                st->ext_fault = true;

        /* VPWR and VIN are always enabled */
        __set_bit(LTC4283_CHAN_VIN, &st->ch_enable_mask);
        __set_bit(LTC4283_CHAN_VPWR, &st->ch_enable_mask);
        for (c = LTC4283_CHAN_ADI_1; c < LTC4283_CHAN_MAX; c++) {
                u32 chan = c - LTC4283_CHAN_ADI_1, bit;

                ret = regmap_read(st->map, LTC4283_ADC_SELECT(chan), &reg_val);
                if (ret)
                        return ret;

                bit = LTC4283_ADC_SELECT_MASK(chan);
                if (c > LTC4283_CHAN_DRAIN)
                        /* account for two reserved fields after DRAIN */
                        bit <<= 2;

                if (!(bit & reg_val))
                        continue;

                __set_bit(c, &st->ch_enable_mask);
        }

        return 0;
}

static const char * const ltc4283_pgio1_funcs[] = {
        "inverted_power_good", "power_good", "gpio"
};

static const char * const ltc4283_pgio2_funcs[] = {
         "inverted_power_good", "power_good", "gpio", "active_current_limiting"
};

static const char * const ltc4283_pgio3_funcs[] = {
        "inverted_power_good_input", "power_good_input", "gpio"
};

static const char * const ltc4283_pgio4_funcs[] = {
        "inverted_external_fault", "external_fault", "gpio"
};

enum {
        LTC4283_PIN_ADIO1,
        LTC4283_PIN_ADIO2,
        LTC4283_PIN_ADIO3,
        LTC4283_PIN_ADIO4,
        LTC4283_PIN_PGIO1,
        LTC4283_PIN_PGIO2,
        LTC4283_PIN_PGIO3,
        LTC4283_PIN_PGIO4,
};

static int ltc4283_pgio_config(struct ltc4283_hwmon *st, struct device *dev)
{
        int ret, func;

        func = device_property_match_property_string(dev, "adi,pgio1-func",
                                                     ltc4283_pgio1_funcs,
                                                     ARRAY_SIZE(ltc4283_pgio1_funcs));
        if (func < 0 && func != -EINVAL)
                return dev_err_probe(dev, func,
                                     "Invalid adi,pgio1-func property\n");
        if (func >= 0) {
                if (func == LTC4283_PGIO_FUNC_GPIO) {
                        __set_bit(LTC4283_PIN_PGIO1, &st->gpio_mask);
                        /* If GPIO, default to an input pin. */
                        func++;
                }

                ret = regmap_update_bits(st->map, LTC4283_PGIO_CONFIG,
                                         LTC4283_PGIO1_CFG_MASK,
                                         FIELD_PREP(LTC4283_PGIO1_CFG_MASK, func));
                if (ret)
                        return ret;
        }

        func = device_property_match_property_string(dev, "adi,pgio2-func",
                                                     ltc4283_pgio2_funcs,
                                                     ARRAY_SIZE(ltc4283_pgio2_funcs));

        if (func < 0 && func != -EINVAL)
                return dev_err_probe(dev, func,
                                     "Invalid adi,pgio2-func property\n");
        if (func >= 0) {
                if (func != LTC4283_PGIO2_FUNC_ACLB) {
                        if (func == LTC4283_PGIO_FUNC_GPIO)  {
                                __set_bit(LTC4283_PIN_PGIO2, &st->gpio_mask);
                                func++;
                        }

                        ret = regmap_update_bits(st->map, LTC4283_PGIO_CONFIG,
                                                 LTC4283_PGIO2_CFG_MASK,
                                                 FIELD_PREP(LTC4283_PGIO2_CFG_MASK, func));
                } else {
                        ret = regmap_set_bits(st->map, LTC4283_CONTROL_1,
                                              LTC4283_PIGIO2_ACLB_MASK);
                }

                if (ret)
                        return ret;
        }

        func = device_property_match_property_string(dev, "adi,pgio3-func",
                                                     ltc4283_pgio3_funcs,
                                                     ARRAY_SIZE(ltc4283_pgio3_funcs));

        if (func < 0 && func != -EINVAL)
                return dev_err_probe(dev, func,
                                     "Invalid adi,pgio3-func property\n");
        if (func >= 0) {
                if (func == LTC4283_PGIO_FUNC_GPIO) {
                        __set_bit(LTC4283_PIN_PGIO3, &st->gpio_mask);
                        func++;
                }

                ret = regmap_update_bits(st->map, LTC4283_PGIO_CONFIG,
                                         LTC4283_PGIO3_CFG_MASK,
                                         FIELD_PREP(LTC4283_PGIO3_CFG_MASK, func));
                if (ret)
                        return ret;
        }

        func = device_property_match_property_string(dev, "adi,pgio4-func",
                                                     ltc4283_pgio4_funcs,
                                                     ARRAY_SIZE(ltc4283_pgio4_funcs));

        if (func < 0 && func != -EINVAL)
                return dev_err_probe(dev, func,
                                     "Invalid adi,pgio4-func property\n");
        if (func >= 0) {
                if (func == LTC4283_PGIO_FUNC_GPIO) {
                        __set_bit(LTC4283_PIN_PGIO4, &st->gpio_mask);
                        func++;
                        st->ext_fault = false;
                } else {
                        st->ext_fault = true;
                }

                ret = regmap_update_bits(st->map, LTC4283_PGIO_CONFIG,
                                         LTC4283_PGIO4_CFG_MASK,
                                         FIELD_PREP(LTC4283_PGIO4_CFG_MASK, func));
                if (ret)
                        return ret;
        }

        return 0;
}

static int ltc4283_adio_config(struct ltc4283_hwmon *st, struct device *dev,
                               const char *prop, u32 pin)
{
        u32 adc_idx;
        int ret;

        if (!device_property_read_bool(dev, prop))
                return 0;

        adc_idx = LTC4283_CHAN_ADIO_1 - LTC4283_CHAN_ADI_1 + pin;
        ret = regmap_clear_bits(st->map, LTC4283_ADC_SELECT(adc_idx),
                                LTC4283_ADC_SELECT_MASK(adc_idx));
        if (ret)
                return ret;

        __set_bit(pin, &st->gpio_mask);
        return 0;
}

static int ltc4283_pin_config(struct ltc4283_hwmon *st, struct device *dev)
{
        int ret;

        ret = ltc4283_pgio_config(st, dev);
        if (ret)
                return ret;

        ret = ltc4283_adio_config(st, dev, "adi,gpio-on-adio1", LTC4283_PIN_ADIO1);
        if (ret)
                return ret;

        ret = ltc4283_adio_config(st, dev, "adi,gpio-on-adio2", LTC4283_PIN_ADIO2);
        if (ret)
                return ret;

        ret = ltc4283_adio_config(st, dev, "adi,gpio-on-adio3", LTC4283_PIN_ADIO3);
        if (ret)
                return ret;

        return ltc4283_adio_config(st, dev, "adi,gpio-on-adio4", LTC4283_PIN_ADIO4);
}

static const char * const ltc4283_oc_fet_retry[] = {
        "latch-off", "1", "7", "unlimited"
};

static const u32 ltc4283_fb_factor[] = {
        100, 50, 20, 10
};

static const u32 ltc4283_cooling_dl[] = {
        512, 1002, 2005, 4100, 8190, 16400, 32800, 65600
};

static const u32 ltc4283_fet_bad_delay[] = {
        256, 512, 1002, 2005
};

static int ltc4283_setup(struct ltc4283_hwmon *st, struct device *dev)
{
        u32 val;
        int ret;

        /* The part has an eeprom so let's get the needed defaults from it */
        ret = ltc4283_get_defaults(st);
        if (ret)
                return ret;

        /*
         * Default to LTC4283_MIN_RSENSE so we can probe without FW properties.
         */
        st->rsense = LTC4283_MIN_RSENSE;
        ret = device_property_read_u32(dev, "adi,rsense-nano-ohms",
                                       &st->rsense);
        if (!ret) {
                if (st->rsense < LTC4283_MIN_RSENSE || st->rsense > LTC4283_MAX_RSENSE)
                        return dev_err_probe(dev, -EINVAL,
                                             "adi,rsense-nano-ohms(%u) too small or too large [%u %u]\n",
                                             st->rsense, LTC4283_MIN_RSENSE, LTC4283_MAX_RSENSE);
        }

        /*
         * The resolution for rsense is tenths of micro (eg: 62.5 uOhm) which
         * means we need nano in the bindings. However, to make things easier to
         * handle (with respect to overflows) we divide it by 100 as we don't
         * really need the last two digits.
         */
        st->rsense /= CENTI;

        ret = device_property_read_u32(dev, "adi,current-limit-sense-microvolt",
                                       &st->vsense_max);
        if (!ret) {
                u32 reg_val;

                if (!in_range(st->vsense_max, LTC4283_VILIM_MIN_uV,
                              LTC4283_VILIM_RANGE)) {
                        return dev_err_probe(dev, -EINVAL,
                                             "adi,current-limit-sense-microvolt (%u) out of range [%u %u]\n",
                                             st->vsense_max, LTC4283_VILIM_MIN_uV,
                                             LTC4283_VILIM_MAX_uV);
                }

                st->vsense_max /= MILLI;
                reg_val = FIELD_PREP(LTC4283_ILIM_MASK,
                                     st->vsense_max - LTC4283_VILIM_MIN_uV / MILLI);
                ret = regmap_update_bits(st->map, LTC4283_CONFIG_1,
                                         LTC4283_ILIM_MASK, reg_val);
                if (ret)
                        return ret;
        }

        ret = ltc4283_parse_array_prop(st, dev, "adi,current-limit-foldback-factor",
                                       ltc4283_fb_factor, ARRAY_SIZE(ltc4283_fb_factor));
        if (ret < 0)
                return ret;
        if (ret < ARRAY_SIZE(ltc4283_fb_factor)) {
                ret = regmap_update_bits(st->map, LTC4283_CONFIG_1, LTC4283_FB_MASK,
                                         FIELD_PREP(LTC4283_FB_MASK, ret));
                if (ret)
                        return ret;
        }

        ret = ltc4283_parse_array_prop(st, dev, "adi,cooling-delay-ms",
                                       ltc4283_cooling_dl, ARRAY_SIZE(ltc4283_cooling_dl));
        if (ret < 0)
                return ret;
        if (ret < ARRAY_SIZE(ltc4283_cooling_dl)) {
                ret = regmap_update_bits(st->map, LTC4283_CONFIG_2, LTC4283_COOLING_DL_MASK,
                                         FIELD_PREP(LTC4283_COOLING_DL_MASK, ret));
                if (ret)
                        return ret;
        }

        ret = ltc4283_parse_array_prop(st, dev, "adi,fet-bad-timer-delay-ms",
                                       ltc4283_fet_bad_delay, ARRAY_SIZE(ltc4283_fet_bad_delay));
        if (ret < 0)
                return ret;
        if (ret < ARRAY_SIZE(ltc4283_fet_bad_delay)) {
                ret = regmap_update_bits(st->map, LTC4283_CONFIG_2, LTC4283_FTBD_DL_MASK,
                                         FIELD_PREP(LTC4283_FTBD_DL_MASK, ret));
                if (ret)
                        return ret;
        }

        ret = ltc4283_set_max_limits(st, dev);
        if (ret)
                return ret;

        ret = ltc4283_pin_config(st, dev);
        if (ret)
                return ret;

        if (device_property_read_bool(dev, "adi,power-good-reset-on-fet")) {
                ret = regmap_clear_bits(st->map, LTC4283_CONTROL_1,
                                        LTC4283_PWRGD_RST_CTRL_MASK);
                if (ret)
                        return ret;
        }

        if (device_property_read_bool(dev, "adi,fet-turn-off-disable")) {
                ret = regmap_clear_bits(st->map, LTC4283_CONTROL_1,
                                        LTC4283_FET_BAD_OFF_MASK);
                if (ret)
                        return ret;
        }

        if (device_property_read_bool(dev, "adi,tmr-pull-down-disable")) {
                ret = regmap_set_bits(st->map, LTC4283_CONTROL_1,
                                      LTC4283_THERM_TMR_MASK);
                if (ret)
                        return ret;
        }

        if (device_property_read_bool(dev, "adi,dvdt-inrush-control-disable")) {
                ret = regmap_clear_bits(st->map, LTC4283_CONTROL_1,
                                        LTC4283_DVDT_MASK);
                if (ret)
                        return ret;
        }

        if (device_property_read_bool(dev, "adi,undervoltage-retry-disable")) {
                ret = regmap_clear_bits(st->map, LTC4283_CONTROL_2,
                                        LTC4283_UV_RETRY_MASK);
                if (ret)
                        return ret;
        }

        if (device_property_read_bool(dev, "adi,overvoltage-retry-disable")) {
                ret = regmap_clear_bits(st->map, LTC4283_CONTROL_2,
                                        LTC4283_OV_RETRY_MASK);
                if (ret)
                        return ret;
        }

        if (device_property_read_bool(dev, "adi,external-fault-retry-enable")) {
                if (!st->ext_fault)
                        return dev_err_probe(dev, -EINVAL,
                                             "adi,external-fault-retry-enable set but PGIO4 not configured\n");
                ret = regmap_set_bits(st->map, LTC4283_CONTROL_2,
                                      LTC4283_EXT_FAULT_RETRY_MASK);
                if (ret)
                        return ret;
        }

        if (device_property_read_bool(dev, "adi,fault-log-enable")) {
                ret = regmap_set_bits(st->map, LTC4283_FAULT_LOG_CTRL,
                                      LTC4283_FAULT_LOG_EN_MASK);
                if (ret)
                        return ret;
        }

        ret = device_property_match_property_string(dev, "adi,overcurrent-retries",
                                                    ltc4283_oc_fet_retry,
                                                    ARRAY_SIZE(ltc4283_oc_fet_retry));
        /* We still want to catch when an invalid string is given. */
        if (ret < 0 && ret != -EINVAL)
                return dev_err_probe(dev, ret,
                                     "adi,overcurrent-retries invalid value\n");
        if (ret >= 0) {
                ret = regmap_update_bits(st->map, LTC4283_CONTROL_2,
                                         LTC4283_OC_RETRY_MASK,
                                         FIELD_PREP(LTC4283_OC_RETRY_MASK, ret));
                if (ret)
                        return ret;
        }

        ret = device_property_match_property_string(dev, "adi,fet-bad-retries",
                                                    ltc4283_oc_fet_retry,
                                                    ARRAY_SIZE(ltc4283_oc_fet_retry));
        if (ret < 0 && ret != -EINVAL)
                return dev_err_probe(dev, ret,
                                     "adi,fet-bad-retries invalid value\n");
        if (ret >= 0) {
                ret = regmap_update_bits(st->map, LTC4283_CONTROL_2,
                                         LTC4283_FET_BAD_RETRY_MASK,
                                         FIELD_PREP(LTC4283_FET_BAD_RETRY_MASK, ret));
                if (ret)
                        return ret;
        }

        if (device_property_read_bool(dev, "adi,external-fault-fet-off-enable")) {
                if (!st->ext_fault)
                        return dev_err_probe(dev, -EINVAL,
                                             "adi,external-fault-fet-off-enable set but PGIO4 not configured\n");
                ret = regmap_set_bits(st->map, LTC4283_CONFIG_3,
                                      LTC4283_EXTFLT_TURN_OFF_MASK);
                if (ret)
                        return ret;
        }

        if (device_property_read_bool(dev, "adi,vpower-drns-enable")) {
                u32 chan = LTC4283_CHAN_DRNS - LTC4283_CHAN_ADI_1;

                __clear_bit(LTC4283_CHAN_DRNS, &st->ch_enable_mask);
                /*
                 * Then, let's by default disable DRNS from ADC2 given that it
                 * is already being monitored by the VPWR channel. One can still
                 * enable it later on if needed.
                 */
                ret = regmap_clear_bits(st->map, LTC4283_ADC_SELECT(chan),
                                        LTC4283_ADC_SELECT_MASK(chan));
                if (ret)
                        return ret;

                val = 1;
        } else {
                val = 0;
        }

        ret = regmap_update_bits(st->map, LTC4283_CONFIG_3,
                                 LTC4283_VPWR_DRNS_MASK,
                                 FIELD_PREP(LTC4283_VPWR_DRNS_MASK, val));
        if (ret)
                return ret;

        /* Make sure the ADC has 12bit resolution since we're assuming that. */
        ret = regmap_update_bits(st->map, LTC4283_PGIO_CONFIG_2,
                                 LTC4283_ADC_MASK,
                                 FIELD_PREP(LTC4283_ADC_MASK, 3));
        if (ret)
                return ret;

        /* Energy reads (which are 6 byte block reads) rely on page access */
        ret = regmap_set_bits(st->map, LTC4283_CONTROL_1, LTC4283_RW_PAGE_MASK);
        if (ret)
                return ret;

        /*
         * Make sure we are integrating power as we only support reporting
         * consumed energy.
         */
        return regmap_clear_bits(st->map, LTC4283_METER_CONTROL,
                                 LTC4283_INTEGRATE_I_MASK);
}

static const struct hwmon_channel_info * const ltc4283_info[] = {
        HWMON_CHANNEL_INFO(in,
                           HWMON_I_LCRIT_ALARM | HWMON_I_CRIT_ALARM |
                           HWMON_I_RESET_HISTORY | HWMON_I_LABEL,
                           HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
                           HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
                           HWMON_I_MAX_ALARM | HWMON_I_RESET_HISTORY |
                           HWMON_I_LABEL,
                           HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
                           HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
                           HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
                           HWMON_I_ENABLE | HWMON_I_LABEL,
                           HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
                           HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
                           HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
                           HWMON_I_ENABLE | HWMON_I_LABEL,
                           HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
                           HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
                           HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
                           HWMON_I_ENABLE | HWMON_I_LABEL,
                           HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
                           HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
                           HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
                           HWMON_I_ENABLE | HWMON_I_LABEL,
                           HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
                           HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
                           HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
                           HWMON_I_ENABLE | HWMON_I_LABEL,
                           HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
                           HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
                           HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
                           HWMON_I_ENABLE | HWMON_I_LABEL,
                           HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
                           HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
                           HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
                           HWMON_I_ENABLE | HWMON_I_LABEL,
                           HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
                           HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
                           HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
                           HWMON_I_ENABLE | HWMON_I_LABEL,
                           HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
                           HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
                           HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
                           HWMON_I_ENABLE | HWMON_I_LABEL,
                           HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
                           HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
                           HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
                           HWMON_I_FAULT | HWMON_I_ENABLE | HWMON_I_LABEL,
                           HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
                           HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
                           HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
                           HWMON_I_ENABLE | HWMON_I_LABEL,
                           HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
                           HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
                           HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
                           HWMON_I_ENABLE | HWMON_I_LABEL,
                           HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
                           HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
                           HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
                           HWMON_I_ENABLE | HWMON_I_LABEL,
                           HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
                           HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
                           HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
                           HWMON_I_ENABLE | HWMON_I_LABEL),
        HWMON_CHANNEL_INFO(curr,
                           HWMON_C_INPUT | HWMON_C_LOWEST | HWMON_C_HIGHEST |
                           HWMON_C_MAX | HWMON_C_MIN | HWMON_C_MIN_ALARM |
                           HWMON_C_MAX_ALARM | HWMON_C_CRIT_ALARM |
                           HWMON_C_RESET_HISTORY | HWMON_C_LABEL),
        HWMON_CHANNEL_INFO(power,
                           HWMON_P_INPUT | HWMON_P_INPUT_LOWEST |
                           HWMON_P_INPUT_HIGHEST | HWMON_P_MAX | HWMON_P_MIN |
                           HWMON_P_MAX_ALARM | HWMON_P_MIN_ALARM |
                           HWMON_P_RESET_HISTORY | HWMON_P_LABEL),
        HWMON_CHANNEL_INFO(energy,
                           HWMON_E_ENABLE),
        HWMON_CHANNEL_INFO(energy64,
                           HWMON_E_INPUT),
        NULL
};

static const struct hwmon_ops ltc4283_ops = {
        .read = ltc4283_read,
        .write = ltc4283_write,
        .is_visible = ltc4283_is_visible,
        .read_string = ltc4283_read_labels,
};

static const struct hwmon_chip_info ltc4283_chip_info = {
        .ops = &ltc4283_ops,
        .info = ltc4283_info,
};

static int ltc4283_show_fault_log(void *arg, u64 *val, u32 mask)
{
        struct ltc4283_hwmon *st = arg;
        long alarm;
        int ret;

        ret = ltc4283_read_alarm(st, LTC4283_FAULT_LOG, mask, &alarm);
        if (ret)
                return ret;

        *val = alarm;

        return 0;
}

static int ltc4283_show_in0_lcrit_fault_log(void *arg, u64 *val)
{
        return ltc4283_show_fault_log(arg, val, LTC4283_UV_FAULT_MASK);
}
DEFINE_DEBUGFS_ATTRIBUTE(ltc4283_in0_lcrit_fault_log,
                         ltc4283_show_in0_lcrit_fault_log, NULL, "%llu\n");

static int ltc4283_show_in0_crit_fault_log(void *arg, u64 *val)
{
        return ltc4283_show_fault_log(arg, val, LTC4283_OV_FAULT_MASK);
}
DEFINE_DEBUGFS_ATTRIBUTE(ltc4283_in0_crit_fault_log,
                         ltc4283_show_in0_crit_fault_log, NULL, "%llu\n");

static int ltc4283_show_fet_bad_fault_log(void *arg, u64 *val)
{
        return ltc4283_show_fault_log(arg, val, LTC4283_FET_BAD_FAULT_MASK);
}
DEFINE_DEBUGFS_ATTRIBUTE(ltc4283_fet_bad_fault_log,
                         ltc4283_show_fet_bad_fault_log, NULL, "%llu\n");

static int ltc4283_show_fet_short_fault_log(void *arg, u64 *val)
{
        return ltc4283_show_fault_log(arg, val, LTC4283_FET_SHORT_FAULT_MASK);
}
DEFINE_DEBUGFS_ATTRIBUTE(ltc4283_fet_short_fault_log,
                         ltc4283_show_fet_short_fault_log, NULL, "%llu\n");

static int ltc4283_show_curr1_crit_fault_log(void *arg, u64 *val)
{
        return ltc4283_show_fault_log(arg, val, LTC4283_OC_FAULT_MASK);
}
DEFINE_DEBUGFS_ATTRIBUTE(ltc4283_curr1_crit_fault_log,
                         ltc4283_show_curr1_crit_fault_log, NULL, "%llu\n");

static int ltc4283_show_power1_failed_fault_log(void *arg, u64 *val)
{
        return ltc4283_show_fault_log(arg, val, LTC4283_PWR_FAIL_FAULT_MASK);
}
DEFINE_DEBUGFS_ATTRIBUTE(ltc4283_power1_failed_fault_log,
                         ltc4283_show_power1_failed_fault_log, NULL, "%llu\n");

static int ltc4283_show_power1_good_input_fault_log(void *arg, u64 *val)
{
        return ltc4283_show_fault_log(arg, val, LTC4283_PGI_FAULT_MASK);
}
DEFINE_DEBUGFS_ATTRIBUTE(ltc4283_power1_good_input_fault_log,
                         ltc4283_show_power1_good_input_fault_log, NULL, "%llu\n");

static void ltc4283_debugfs_init(struct ltc4283_hwmon *st, struct i2c_client *i2c)
{
        debugfs_create_file_unsafe("in0_crit_fault_log", 0400, i2c->debugfs, st,
                                   &ltc4283_in0_crit_fault_log);
        debugfs_create_file_unsafe("in0_lcrit_fault_log", 0400, i2c->debugfs, st,
                                   &ltc4283_in0_lcrit_fault_log);
        debugfs_create_file_unsafe("in11_fet_bad_fault_log", 0400, i2c->debugfs, st,
                                   &ltc4283_fet_bad_fault_log);
        debugfs_create_file_unsafe("in11_fet_short_fault_log", 0400, i2c->debugfs, st,
                                   &ltc4283_fet_short_fault_log);
        debugfs_create_file_unsafe("curr1_crit_fault_log", 0400, i2c->debugfs, st,
                                   &ltc4283_curr1_crit_fault_log);
        debugfs_create_file_unsafe("power1_failed_fault_log", 0400, i2c->debugfs, st,
                                   &ltc4283_power1_failed_fault_log);
        debugfs_create_file_unsafe("power1_good_input_fault_log", 0400, i2c->debugfs,
                                   st, &ltc4283_power1_good_input_fault_log);
}

static bool ltc4283_is_word_reg(unsigned int reg)
{
        return reg >= LTC4283_SENSE && reg <= LTC4283_ADIO34_MAX;
}

static int ltc4283_reg_read(void *context, unsigned int reg, unsigned int *val)
{
        struct i2c_client *client = context;
        int ret;

        if (ltc4283_is_word_reg(reg))
                ret = i2c_smbus_read_word_swapped(client, reg);
        else
                ret = i2c_smbus_read_byte_data(client, reg);

        if (ret < 0)
                return ret;

        *val = ret;
        return 0;
}

static int ltc4283_reg_write(void *context, unsigned int reg, unsigned int val)
{
        struct i2c_client *client = context;

        if (ltc4283_is_word_reg(reg))
                return i2c_smbus_write_word_swapped(client, reg, val);

        return i2c_smbus_write_byte_data(client, reg, val);
}

static const struct regmap_bus ltc4283_regmap_bus = {
        .reg_read = ltc4283_reg_read,
        .reg_write = ltc4283_reg_write,
};

static bool ltc4283_writable_reg(struct device *dev, unsigned int reg)
{
        switch (reg) {
        case LTC4283_SYSTEM_STATUS ... LTC4283_FAULT_STATUS:
                return false;
        case LTC4283_RESERVED_OC:
                return false;
        case LTC4283_RESERVED_86 ... LTC4283_RESERVED_8F:
                return false;
        case LTC4283_RESERVED_91 ... LTC4283_RESERVED_A1:
                return false;
        case LTC4283_RESERVED_A3:
                return false;
        case LTC4283_RESERVED_AC:
                return false;
        case LTC4283_POWER_PLAY_MSB ... LTC4283_POWER_PLAY_LSB:
                return false;
        case LTC4283_RESERVED_F1 ... LTC4283_RESERVED_FF:
                return false;
        default:
                return true;
        }
}

static const struct regmap_config ltc4283_regmap_config = {
        .reg_bits = 8,
        .val_bits = 16,
        .max_register = 0xFF,
        .writeable_reg = ltc4283_writable_reg,
};

static int ltc4283_probe(struct i2c_client *client)
{
        struct device *dev = &client->dev, *hwmon;
        struct auxiliary_device *adev;
        struct ltc4283_hwmon *st;
        int ret, id;

        st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL);
        if (!st)
                return -ENOMEM;

        if (!i2c_check_functionality(client->adapter,
                                     I2C_FUNC_SMBUS_BYTE_DATA |
                                     I2C_FUNC_SMBUS_WORD_DATA |
                                     I2C_FUNC_SMBUS_READ_I2C_BLOCK))
                return -EOPNOTSUPP;

        st->client = client;
        st->map = devm_regmap_init(dev, &ltc4283_regmap_bus, client,
                                   &ltc4283_regmap_config);
        if (IS_ERR(st->map))
                return dev_err_probe(dev, PTR_ERR(st->map),
                                     "Failed to create regmap\n");

        ret = ltc4283_setup(st, dev);
        if (ret)
                return ret;

        hwmon = devm_hwmon_device_register_with_info(dev, "ltc4283", st,
                                                     &ltc4283_chip_info, NULL);

        if (IS_ERR(hwmon))
                return PTR_ERR(hwmon);

        ltc4283_debugfs_init(st, client);

        if (!st->gpio_mask)
                return 0;

        id = (client->adapter->nr << 10) | client->addr;
        adev = __devm_auxiliary_device_create(dev, KBUILD_MODNAME, "gpio",
                                              &st->gpio_mask, id);
        if (!adev)
                return dev_err_probe(dev, -ENODEV, "Failed to add GPIO device\n");

        return 0;
}

static const struct of_device_id ltc4283_of_match[] = {
        { .compatible = "adi,ltc4283" },
        { }
};

static const struct i2c_device_id ltc4283_i2c_id[] = {
        { "ltc4283" },
        { }
};
MODULE_DEVICE_TABLE(i2c, ltc4283_i2c_id);

static struct i2c_driver ltc4283_driver = {
        .driver = {
                .name = "ltc4283",
                .of_match_table = ltc4283_of_match,
        },
        .probe = ltc4283_probe,
        .id_table = ltc4283_i2c_id,
};
module_i2c_driver(ltc4283_driver);

MODULE_AUTHOR("Nuno Sá <nuno.sa@analog.com>");
MODULE_DESCRIPTION("LTC4283 Hot Swap Controller driver");
MODULE_LICENSE("GPL");