--- zzzz-none-000/linux-5.4.213/drivers/hwmon/ina2xx.c 2022-09-15 10:04:56.000000000 +0000 +++ alder-5690pro-762/linux-5.4.213/drivers/hwmon/ina2xx.c 2024-08-14 09:01:57.000000000 +0000 @@ -18,6 +18,10 @@ * Bi-directional Current/Power Monitor with I2C Interface * Datasheet: http://www.ti.com/product/ina230 * + * INA234: + * Bi-directional Current/Power Monitor with I2C Interface + * Datasheet: http://www.ti.com/product/ina234 + * * Copyright (C) 2012 Lothar Felten * Thanks to Jan Volkering */ @@ -36,9 +40,14 @@ #include #include #include +#include +#include +#include #include +#include + /* common register definitions */ #define INA2XX_CONFIG 0x00 #define INA2XX_SHUNT_VOLTAGE 0x01 /* readonly */ @@ -52,11 +61,17 @@ #define INA226_ALERT_LIMIT 0x07 #define INA226_DIE_ID 0xFF +/* INA234 register definitions */ +#define INA23X_MANUFACTURER_ID 0x3E +#define INA23X_DEVICE_ID 0x3F + /* register count */ #define INA219_REGISTERS 6 #define INA226_REGISTERS 8 +#define ina236_REGISTERS 8 +#define INA234_REGISTERS 9 -#define INA2XX_MAX_REGISTERS 8 +#define INA2XX_MAX_REGISTERS 9 /* settings - depend on use case */ #define INA219_CONFIG_DEFAULT 0x399F /* PGA=8 */ @@ -74,6 +89,26 @@ #define INA226_READ_AVG(reg) (((reg) & INA226_AVG_RD_MASK) >> 9) #define INA226_SHIFT_AVG(val) ((val) << 9) +/* bit number of alert functions in Mask/Enable Register */ +#define INA226_MAX_ALERT_BIT 16 +#define INA226_SHUNT_OVER_VOLTAGE_BIT 15 +#define INA226_SHUNT_UNDER_VOLTAGE_BIT 14 +#define INA226_BUS_OVER_VOLTAGE_BIT 13 +#define INA226_BUS_UNDER_VOLTAGE_BIT 12 +#define INA226_POWER_OVER_LIMIT_BIT 11 + +#define ALERT_TYPE_OFFSET 10 +static char *alert_type[] = { "Unknown alert type", + "Power overlimit", + "Bus undervoltage", + "Bus overvoltage", + "Shunt undervoltage", + "Shunt overvoltage" }; + +/* bit mask for alert config bits of Mask/Enable Register */ +#define INA226_ALERT_CONFIG_MASK 0xFC00 +#define INA226_ALERT_FUNCTION_FLAG BIT(4) + /* common attrs, ina226 attrs and NULL */ #define INA2XX_MAX_ATTRIBUTE_GROUPS 3 @@ -88,14 +123,16 @@ .val_bits = 16, }; -enum ina2xx_ids { ina219, ina226 }; +enum ina2xx_ids { ina219, ina226, ina234, ina236 }; struct ina2xx_config { u16 config_default; int calibration_value; int registers; int shunt_div; + int shunt_voltage_shift; int bus_voltage_shift; + int current_shift; int bus_voltage_lsb; /* uV */ int power_lsb_factor; }; @@ -108,6 +145,9 @@ long power_lsb_uW; struct mutex config_lock; struct regmap *regmap; + enum ina2xx_ids chip; + int alert_type; + int alert_irq; const struct attribute_group *groups[INA2XX_MAX_ATTRIBUTE_GROUPS]; }; @@ -131,6 +171,26 @@ .bus_voltage_lsb = 1250, .power_lsb_factor = 25, }, + [ina234] = { + .config_default = INA226_CONFIG_DEFAULT, + .calibration_value = 2048, + .registers = INA234_REGISTERS, + .shunt_div = 25, + .shunt_voltage_shift = 4, + .bus_voltage_shift = 4, + .current_shift = 4, + .bus_voltage_lsb = 25600, + .power_lsb_factor = 32, + }, + [ina236] = { + .config_default = INA226_CONFIG_DEFAULT, + .calibration_value = 2048, + .registers = ina236_REGISTERS, + .shunt_div = 400, + .bus_voltage_shift = 0, + .bus_voltage_lsb = 1600, + .power_lsb_factor = 32, + }, }; /* @@ -259,7 +319,8 @@ switch (reg) { case INA2XX_SHUNT_VOLTAGE: /* signed register */ - val = DIV_ROUND_CLOSEST((s16)regval, data->config->shunt_div); + val = shift_right((s16)regval, data->config->shunt_voltage_shift); + val = DIV_ROUND_CLOSEST((s16)val, data->config->shunt_div); break; case INA2XX_BUS_VOLTAGE: val = (regval >> data->config->bus_voltage_shift) @@ -271,7 +332,8 @@ break; case INA2XX_CURRENT: /* signed register, result in mA */ - val = (s16)regval * data->current_lsb_uA; + val = shift_right((s16)regval, data->config->current_shift); + val = (s16)val * data->current_lsb_uA; val = DIV_ROUND_CLOSEST(val, 1000); break; case INA2XX_CALIBRATION: @@ -303,6 +365,167 @@ ina2xx_get_value(data, attr->index, regval)); } +static int ina226_reg_to_alert(struct ina2xx_data *data, u8 bit, u16 regval) +{ + int reg; + + switch (bit) { + case INA226_SHUNT_OVER_VOLTAGE_BIT: + case INA226_SHUNT_UNDER_VOLTAGE_BIT: + reg = INA2XX_SHUNT_VOLTAGE; + break; + case INA226_BUS_OVER_VOLTAGE_BIT: + case INA226_BUS_UNDER_VOLTAGE_BIT: + reg = INA2XX_BUS_VOLTAGE; + break; + case INA226_POWER_OVER_LIMIT_BIT: + reg = INA2XX_POWER; + break; + default: + /* programmer goofed */ + WARN_ON_ONCE(1); + return 0; + } + + return ina2xx_get_value(data, reg, regval); +} + +/* + * Turns alert limit values into register values. + * Opposite of the formula in ina2xx_get_value(). + */ +static s16 ina226_alert_to_reg(struct ina2xx_data *data, u8 bit, int val) +{ + switch (bit) { + case INA226_SHUNT_OVER_VOLTAGE_BIT: + case INA226_SHUNT_UNDER_VOLTAGE_BIT: + val *= data->config->shunt_div; + return clamp_val(val, SHRT_MIN, SHRT_MAX); + case INA226_BUS_OVER_VOLTAGE_BIT: + case INA226_BUS_UNDER_VOLTAGE_BIT: + val = (val * 1000) << data->config->bus_voltage_shift; + val = DIV_ROUND_CLOSEST(val, data->config->bus_voltage_lsb); + return clamp_val(val, 0, SHRT_MAX); + case INA226_POWER_OVER_LIMIT_BIT: + val = DIV_ROUND_CLOSEST(val, data->power_lsb_uW); + return clamp_val(val, 0, USHRT_MAX); + default: + /* programmer goofed */ + WARN_ON_ONCE(1); + return 0; + } +} + +static int ina226_set_alert(struct ina2xx_data *data, u8 bit, int val) +{ + int ret; + + /* + * Clear all alerts first to avoid accidentally triggering ALERT pin + * due to register write sequence. Then, only enable the alert + * if the value is non-zero. + */ + mutex_lock(&data->config_lock); + ret = regmap_update_bits(data->regmap, INA226_MASK_ENABLE, + INA226_ALERT_CONFIG_MASK, 0); + if (ret < 0) + goto abort; + + ret = regmap_write(data->regmap, INA226_ALERT_LIMIT, + ina226_alert_to_reg(data, bit, val)); + if (ret < 0) + goto abort; + + if (val != 0) { + ret = regmap_update_bits(data->regmap, INA226_MASK_ENABLE, + INA226_ALERT_CONFIG_MASK, + BIT(bit)); + if (ret < 0) + goto abort; + } + + data->alert_type = bit; + +abort: + mutex_unlock(&data->config_lock); + return ret; +} + +static ssize_t ina226_alert_show(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct ina2xx_data *data = dev_get_drvdata(dev); + int regval; + int val = 0; + int ret; + + mutex_lock(&data->config_lock); + ret = regmap_read(data->regmap, INA226_MASK_ENABLE, ®val); + if (ret) + goto abort; + + if (regval & BIT(attr->index)) { + ret = regmap_read(data->regmap, INA226_ALERT_LIMIT, ®val); + if (ret) + goto abort; + val = ina226_reg_to_alert(data, attr->index, regval); + } + + ret = snprintf(buf, PAGE_SIZE, "%d\n", val); +abort: + mutex_unlock(&data->config_lock); + return ret; +} + +static ssize_t ina226_alert_store(struct device *dev, + struct device_attribute *da, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct ina2xx_data *data = dev_get_drvdata(dev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret < 0) + return ret; + + ret = ina226_set_alert(data, attr->index, val); + + if (ret == 0) + ret = count; + + return ret; +} + +static ssize_t ina226_alarm_show(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct ina2xx_data *data = dev_get_drvdata(dev); + int regval; + int alarm = 0; + int ret; + + ret = regmap_read(data->regmap, INA226_MASK_ENABLE, ®val); + if (ret) + return ret; + + alarm = (regval & BIT(attr->index)) && + (regval & INA226_ALERT_FUNCTION_FLAG); + return snprintf(buf, PAGE_SIZE, "%d\n", alarm); +} + +static irqreturn_t ina2xx_irq_thread(int irq, void *data) +{ + struct ina2xx_data *d = (struct ina2xx_data *) data; + + pr_emerg("%s detected\n", alert_type[d->alert_type - ALERT_TYPE_OFFSET]); + avm_set_reset_status(RS_DYING_GASP); + return IRQ_HANDLED; +} + /* * In order to keep calibration register value fixed, the product * of current_lsb and shunt_resistor should also be fixed and equal @@ -392,15 +615,38 @@ /* shunt voltage */ static SENSOR_DEVICE_ATTR_RO(in0_input, ina2xx_value, INA2XX_SHUNT_VOLTAGE); +/* shunt voltage over/under voltage alert setting and alarm */ +static SENSOR_DEVICE_ATTR_RW(in0_crit, ina226_alert, + INA226_SHUNT_OVER_VOLTAGE_BIT); +static SENSOR_DEVICE_ATTR_RW(in0_lcrit, ina226_alert, + INA226_SHUNT_UNDER_VOLTAGE_BIT); +static SENSOR_DEVICE_ATTR_RO(in0_crit_alarm, ina226_alarm, + INA226_SHUNT_OVER_VOLTAGE_BIT); +static SENSOR_DEVICE_ATTR_RO(in0_lcrit_alarm, ina226_alarm, + INA226_SHUNT_UNDER_VOLTAGE_BIT); /* bus voltage */ static SENSOR_DEVICE_ATTR_RO(in1_input, ina2xx_value, INA2XX_BUS_VOLTAGE); +/* bus voltage over/under voltage alert setting and alarm */ +static SENSOR_DEVICE_ATTR_RW(in1_crit, ina226_alert, + INA226_BUS_OVER_VOLTAGE_BIT); +static SENSOR_DEVICE_ATTR_RW(in1_lcrit, ina226_alert, + INA226_BUS_UNDER_VOLTAGE_BIT); +static SENSOR_DEVICE_ATTR_RO(in1_crit_alarm, ina226_alarm, + INA226_BUS_OVER_VOLTAGE_BIT); +static SENSOR_DEVICE_ATTR_RO(in1_lcrit_alarm, ina226_alarm, + INA226_BUS_UNDER_VOLTAGE_BIT); /* calculated current */ static SENSOR_DEVICE_ATTR_RO(curr1_input, ina2xx_value, INA2XX_CURRENT); /* calculated power */ static SENSOR_DEVICE_ATTR_RO(power1_input, ina2xx_value, INA2XX_POWER); +/* over-limit power alert setting and alarm */ +static SENSOR_DEVICE_ATTR_RW(power1_crit, ina226_alert, + INA226_POWER_OVER_LIMIT_BIT); +static SENSOR_DEVICE_ATTR_RO(power1_crit_alarm, ina226_alarm, + INA226_POWER_OVER_LIMIT_BIT); /* shunt resistance */ static SENSOR_DEVICE_ATTR_RW(shunt_resistor, ina2xx_shunt, INA2XX_CALIBRATION); @@ -423,6 +669,16 @@ }; static struct attribute *ina226_attrs[] = { + &sensor_dev_attr_in0_crit.dev_attr.attr, + &sensor_dev_attr_in0_lcrit.dev_attr.attr, + &sensor_dev_attr_in0_crit_alarm.dev_attr.attr, + &sensor_dev_attr_in0_lcrit_alarm.dev_attr.attr, + &sensor_dev_attr_in1_crit.dev_attr.attr, + &sensor_dev_attr_in1_lcrit.dev_attr.attr, + &sensor_dev_attr_in1_crit_alarm.dev_attr.attr, + &sensor_dev_attr_in1_lcrit_alarm.dev_attr.attr, + &sensor_dev_attr_power1_crit.dev_attr.attr, + &sensor_dev_attr_power1_crit_alarm.dev_attr.attr, &sensor_dev_attr_update_interval.dev_attr.attr, NULL, }; @@ -437,7 +693,7 @@ struct device *dev = &client->dev; struct ina2xx_data *data; struct device *hwmon_dev; - u32 val; + u32 val, alert_type; int ret, group = 0; enum ina2xx_ids chip; @@ -452,6 +708,7 @@ /* set the device type */ data->config = &ina2xx_config[chip]; + data->chip = chip; mutex_init(&data->config_lock); if (of_property_read_u32(dev->of_node, "shunt-resistor", &val) < 0) { @@ -480,7 +737,7 @@ } data->groups[group++] = &ina2xx_group; - if (chip == ina226) + if (chip == ina226 || chip == ina234 || chip == ina236) data->groups[group++] = &ina226_group; hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, @@ -488,6 +745,35 @@ if (IS_ERR(hwmon_dev)) return PTR_ERR(hwmon_dev); + data->alert_irq = platform_get_irq_byname(to_platform_device(dev), "alert"); + + if (data->alert_irq) { + ret = devm_request_irq(dev, data->alert_irq, + ina2xx_irq_thread, + IRQF_TRIGGER_FALLING, + "ina2xx", + data); + if (ret < 0) { + dev_err(dev, "cannot request IRQ %d\n", data->alert_irq); + return ret; + } + + if (of_property_read_u32(dev->of_node, "alert-type", &val) < 0 || + val <= ALERT_TYPE_OFFSET || val >= INA226_MAX_ALERT_BIT) { + dev_err(dev, "No alert-type or wrong given, please set it via sysfs.\n"); + } else { + alert_type = val; + if (of_property_read_u32(dev->of_node, "alert-limit", &val) < 0) + dev_err(dev, "No alert-limit given, please set it via sysfs.\n"); + else + ina226_set_alert(data, alert_type, val); + } + + dev_info(dev, "IRQ: %d successfully configured\n", data->alert_irq); + } else { + dev_err(dev, "no IRQ configured for %s\n", client->name); + } + dev_info(dev, "power monitor %s (Rshunt = %li uOhm)\n", client->name, data->rshunt); @@ -500,6 +786,8 @@ { "ina226", ina226 }, { "ina230", ina226 }, { "ina231", ina226 }, + { "ina234", ina234 }, + { "ina236", ina236 }, { } }; MODULE_DEVICE_TABLE(i2c, ina2xx_id); @@ -525,6 +813,14 @@ .compatible = "ti,ina231", .data = (void *)ina226 }, + { + .compatible = "ti,ina236", + .data = (void *)ina236 + }, + { + .compatible = "ti,ina234", + .data = (void *)ina234 + }, { }, }; MODULE_DEVICE_TABLE(of, ina2xx_of_match);