--- zzzz-none-000/linux-3.10.107/drivers/hwmon/ina2xx.c 2017-06-27 09:49:32.000000000 +0000 +++ scorpion-7490-727/linux-3.10.107/drivers/hwmon/ina2xx.c 2021-02-04 17:41:59.000000000 +0000 @@ -34,6 +34,10 @@ #include #include #include +#include +#include +#include +#include #include @@ -50,7 +54,6 @@ #define INA226_ALERT_LIMIT 0x07 #define INA226_DIE_ID 0xFF - /* register count */ #define INA219_REGISTERS 6 #define INA226_REGISTERS 8 @@ -63,6 +66,29 @@ /* worst case is 68.10 ms (~14.6Hz, ina219) */ #define INA2XX_CONVERSION_RATE 15 +#define INA2XX_MAX_DELAY 69 /* worst case delay in ms */ + +#define INA2XX_RSHUNT_DEFAULT 10000 + +/* bit mask for reading the averaging setting in the configuration register */ +#define INA226_AVG_RD_MASK 0x0E00 + +#define INA226_READ_AVG(reg) (((reg) & INA226_AVG_RD_MASK) >> 9) +#define INA226_SHIFT_AVG(val) ((val) << 9) + +/* common attrs, ina226 attrs and NULL */ +#define INA2XX_MAX_ATTRIBUTE_GROUPS 3 + +/* + * Both bus voltage and shunt voltage conversion times for ina226 are set + * to 0b0100 on POR, which translates to 2200 microseconds in total. + */ +#define INA226_TOTAL_CONV_TIME_DEFAULT 2200 + +static struct regmap_config ina2xx_regmap_config = { + .reg_bits = 8, + .val_bits = 16, +}; enum ina2xx_ids { ina219, ina226 }; @@ -77,15 +103,13 @@ }; struct ina2xx_data { - struct device *hwmon_dev; const struct ina2xx_config *config; - struct mutex update_lock; - bool valid; - unsigned long last_updated; + long rshunt; + struct mutex config_lock; + struct regmap *regmap; - int kind; - u16 regs[INA2XX_MAX_REGISTERS]; + const struct attribute_group *groups[INA2XX_MAX_ATTRIBUTE_GROUPS]; }; static const struct ina2xx_config ina2xx_config[] = { @@ -109,59 +133,149 @@ }, }; -static struct ina2xx_data *ina2xx_update_device(struct device *dev) +/* + * Available averaging rates for ina226. The indices correspond with + * the bit values expected by the chip (according to the ina226 datasheet, + * table 3 AVG bit settings, found at + * http://www.ti.com/lit/ds/symlink/ina226.pdf. + */ +static const int ina226_avg_tab[] = { 1, 4, 16, 64, 128, 256, 512, 1024 }; + +static int ina226_reg_to_interval(u16 config) +{ + int avg = ina226_avg_tab[INA226_READ_AVG(config)]; + + /* + * Multiply the total conversion time by the number of averages. + * Return the result in milliseconds. + */ + return DIV_ROUND_CLOSEST(avg * INA226_TOTAL_CONV_TIME_DEFAULT, 1000); +} + +/* + * Return the new, shifted AVG field value of CONFIG register, + * to use with regmap_update_bits + */ +static u16 ina226_interval_to_reg(int interval) +{ + int avg, avg_bits; + + avg = DIV_ROUND_CLOSEST(interval * 1000, + INA226_TOTAL_CONV_TIME_DEFAULT); + avg_bits = find_closest(avg, ina226_avg_tab, + ARRAY_SIZE(ina226_avg_tab)); + + return INA226_SHIFT_AVG(avg_bits); +} + +static int ina2xx_calibrate(struct ina2xx_data *data) +{ + u16 val = DIV_ROUND_CLOSEST(data->config->calibration_factor, + data->rshunt); + + return regmap_write(data->regmap, INA2XX_CALIBRATION, val); +} + +/* + * Initialize the configuration and calibration registers. + */ +static int ina2xx_init(struct ina2xx_data *data) { - struct i2c_client *client = to_i2c_client(dev); - struct ina2xx_data *data = i2c_get_clientdata(client); - struct ina2xx_data *ret = data; + int ret = regmap_write(data->regmap, INA2XX_CONFIG, + data->config->config_default); + if (ret < 0) + return ret; - mutex_lock(&data->update_lock); + /* + * Set current LSB to 1mA, shunt is in uOhms + * (equation 13 in datasheet). + */ + return ina2xx_calibrate(data); +} - if (time_after(jiffies, data->last_updated + - HZ / INA2XX_CONVERSION_RATE) || !data->valid) { +static int ina2xx_read_reg(struct device *dev, int reg, unsigned int *regval) +{ + struct ina2xx_data *data = dev_get_drvdata(dev); + int ret, retry; - int i; + dev_dbg(dev, "Starting register %d read\n", reg); - dev_dbg(&client->dev, "Starting ina2xx update\n"); + for (retry = 5; retry; retry--) { - /* Read all registers */ - for (i = 0; i < data->config->registers; i++) { - int rv = i2c_smbus_read_word_swapped(client, i); - if (rv < 0) { - ret = ERR_PTR(rv); - goto abort; + ret = regmap_read(data->regmap, reg, regval); + if (ret < 0) + return ret; + + dev_dbg(dev, "read %d, val = 0x%04x\n", reg, *regval); + + /* + * If the current value in the calibration register is 0, the + * power and current registers will also remain at 0. In case + * the chip has been reset let's check the calibration + * register and reinitialize if needed. + * We do that extra read of the calibration register if there + * is some hint of a chip reset. + */ + if (*regval == 0) { + unsigned int cal; + + ret = regmap_read(data->regmap, INA2XX_CALIBRATION, + &cal); + if (ret < 0) + return ret; + + if (cal == 0) { + dev_warn(dev, "chip not calibrated, reinitializing\n"); + + ret = ina2xx_init(data); + if (ret < 0) + return ret; + /* + * Let's make sure the power and current + * registers have been updated before trying + * again. + */ + msleep(INA2XX_MAX_DELAY); + continue; } - data->regs[i] = rv; } - data->last_updated = jiffies; - data->valid = 1; + return 0; } -abort: - mutex_unlock(&data->update_lock); - return ret; + + /* + * If we're here then although all write operations succeeded, the + * chip still returns 0 in the calibration register. Nothing more we + * can do here. + */ + dev_err(dev, "unable to reinitialize the chip\n"); + return -ENODEV; } -static int ina2xx_get_value(struct ina2xx_data *data, u8 reg) +static int ina2xx_get_value(struct ina2xx_data *data, u8 reg, + unsigned int regval) { int val; switch (reg) { case INA2XX_SHUNT_VOLTAGE: /* signed register */ - val = DIV_ROUND_CLOSEST((s16)data->regs[reg], - data->config->shunt_div); + val = DIV_ROUND_CLOSEST((s16)regval, data->config->shunt_div); break; case INA2XX_BUS_VOLTAGE: - val = (data->regs[reg] >> data->config->bus_voltage_shift) + val = (regval >> data->config->bus_voltage_shift) * data->config->bus_voltage_lsb; val = DIV_ROUND_CLOSEST(val, 1000); break; case INA2XX_POWER: - val = data->regs[reg] * data->config->power_lsb; + val = regval * data->config->power_lsb; break; case INA2XX_CURRENT: /* signed register, LSB=1mA (selected), in mA */ - val = (s16)data->regs[reg]; + val = (s16)regval; + break; + case INA2XX_CALIBRATION: + val = DIV_ROUND_CLOSEST(data->config->calibration_factor, + regval); break; default: /* programmer goofed */ @@ -177,13 +291,81 @@ struct device_attribute *da, char *buf) { struct sensor_device_attribute *attr = to_sensor_dev_attr(da); - struct ina2xx_data *data = ina2xx_update_device(dev); + struct ina2xx_data *data = dev_get_drvdata(dev); + unsigned int regval; - if (IS_ERR(data)) - return PTR_ERR(data); + int err = ina2xx_read_reg(dev, attr->index, ®val); + + if (err < 0) + return err; return snprintf(buf, PAGE_SIZE, "%d\n", - ina2xx_get_value(data, attr->index)); + ina2xx_get_value(data, attr->index, regval)); +} + +static ssize_t ina2xx_set_shunt(struct device *dev, + struct device_attribute *da, + const char *buf, size_t count) +{ + unsigned long val; + int status; + struct ina2xx_data *data = dev_get_drvdata(dev); + + status = kstrtoul(buf, 10, &val); + if (status < 0) + return status; + + if (val == 0 || + /* Values greater than the calibration factor make no sense. */ + val > data->config->calibration_factor) + return -EINVAL; + + mutex_lock(&data->config_lock); + data->rshunt = val; + status = ina2xx_calibrate(data); + mutex_unlock(&data->config_lock); + if (status < 0) + return status; + + return count; +} + +static ssize_t ina226_set_interval(struct device *dev, + struct device_attribute *da, + const char *buf, size_t count) +{ + struct ina2xx_data *data = dev_get_drvdata(dev); + unsigned long val; + int status; + + status = kstrtoul(buf, 10, &val); + if (status < 0) + return status; + + if (val > INT_MAX || val == 0) + return -EINVAL; + + status = regmap_update_bits(data->regmap, INA2XX_CONFIG, + INA226_AVG_RD_MASK, + ina226_interval_to_reg(val)); + if (status < 0) + return status; + + return count; +} + +static ssize_t ina226_show_interval(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct ina2xx_data *data = dev_get_drvdata(dev); + int status; + unsigned int regval; + + status = regmap_read(data->regmap, INA2XX_CONFIG, ®val); + if (status) + return status; + + return snprintf(buf, PAGE_SIZE, "%d\n", ina226_reg_to_interval(regval)); } /* shunt voltage */ @@ -202,85 +384,95 @@ static SENSOR_DEVICE_ATTR(power1_input, S_IRUGO, ina2xx_show_value, NULL, INA2XX_POWER); +/* shunt resistance */ +static SENSOR_DEVICE_ATTR(shunt_resistor, S_IRUGO | S_IWUSR, + ina2xx_show_value, ina2xx_set_shunt, + INA2XX_CALIBRATION); + +/* update interval (ina226 only) */ +static SENSOR_DEVICE_ATTR(update_interval, S_IRUGO | S_IWUSR, + ina226_show_interval, ina226_set_interval, 0); + /* pointers to created device attributes */ -static struct attribute *ina2xx_attributes[] = { +static struct attribute *ina2xx_attrs[] = { &sensor_dev_attr_in0_input.dev_attr.attr, &sensor_dev_attr_in1_input.dev_attr.attr, &sensor_dev_attr_curr1_input.dev_attr.attr, &sensor_dev_attr_power1_input.dev_attr.attr, + &sensor_dev_attr_shunt_resistor.dev_attr.attr, NULL, }; static const struct attribute_group ina2xx_group = { - .attrs = ina2xx_attributes, + .attrs = ina2xx_attrs, +}; + +static struct attribute *ina226_attrs[] = { + &sensor_dev_attr_update_interval.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ina226_group = { + .attrs = ina226_attrs, }; static int ina2xx_probe(struct i2c_client *client, const struct i2c_device_id *id) { - struct i2c_adapter *adapter = client->adapter; + struct device *dev = &client->dev; struct ina2xx_data *data; - struct ina2xx_platform_data *pdata; - int ret; - long shunt = 10000; /* default shunt value 10mOhms */ - - if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) - return -ENODEV; + struct device *hwmon_dev; + u32 val; + int ret, group = 0; - data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; - if (client->dev.platform_data) { - pdata = - (struct ina2xx_platform_data *)client->dev.platform_data; - shunt = pdata->shunt_uohms; + /* set the device type */ + data->config = &ina2xx_config[id->driver_data]; + + if (of_property_read_u32(dev->of_node, "shunt-resistor", &val) < 0) { + struct ina2xx_platform_data *pdata = dev_get_platdata(dev); + + if (pdata) + val = pdata->shunt_uohms; + else + val = INA2XX_RSHUNT_DEFAULT; } - if (shunt <= 0) + if (val <= 0 || val > data->config->calibration_factor) return -ENODEV; - /* set the device type */ - data->kind = id->driver_data; - data->config = &ina2xx_config[data->kind]; - - /* device configuration */ - i2c_smbus_write_word_swapped(client, INA2XX_CONFIG, - data->config->config_default); - /* set current LSB to 1mA, shunt is in uOhms */ - /* (equation 13 in datasheet) */ - i2c_smbus_write_word_swapped(client, INA2XX_CALIBRATION, - data->config->calibration_factor / shunt); + data->rshunt = val; - i2c_set_clientdata(client, data); - mutex_init(&data->update_lock); + ina2xx_regmap_config.max_register = data->config->registers; - ret = sysfs_create_group(&client->dev.kobj, &ina2xx_group); - if (ret) - return ret; - - data->hwmon_dev = hwmon_device_register(&client->dev); - if (IS_ERR(data->hwmon_dev)) { - ret = PTR_ERR(data->hwmon_dev); - goto out_err_hwmon; + data->regmap = devm_regmap_init_i2c(client, &ina2xx_regmap_config); + if (IS_ERR(data->regmap)) { + dev_err(dev, "failed to allocate register map\n"); + return PTR_ERR(data->regmap); } - dev_info(&client->dev, "power monitor %s (Rshunt = %li uOhm)\n", - id->name, shunt); - - return 0; + ret = ina2xx_init(data); + if (ret < 0) { + dev_err(dev, "error configuring the device: %d\n", ret); + return -ENODEV; + } -out_err_hwmon: - sysfs_remove_group(&client->dev.kobj, &ina2xx_group); - return ret; -} + mutex_init(&data->config_lock); -static int ina2xx_remove(struct i2c_client *client) -{ - struct ina2xx_data *data = i2c_get_clientdata(client); + data->groups[group++] = &ina2xx_group; + if (id->driver_data == ina226) + data->groups[group++] = &ina226_group; + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, data->groups); + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); - hwmon_device_unregister(data->hwmon_dev); - sysfs_remove_group(&client->dev.kobj, &ina2xx_group); + dev_info(dev, "power monitor %s (Rshunt = %li uOhm)\n", + id->name, data->rshunt); return 0; } @@ -290,6 +482,7 @@ { "ina220", ina219 }, { "ina226", ina226 }, { "ina230", ina226 }, + { "ina231", ina226 }, { } }; MODULE_DEVICE_TABLE(i2c, ina2xx_id); @@ -299,7 +492,6 @@ .name = "ina2xx", }, .probe = ina2xx_probe, - .remove = ina2xx_remove, .id_table = ina2xx_id, };