cregit-Linux how code gets into the kernel

Release 4.11 drivers/hwmon/ina2xx.c

Directory: drivers/hwmon
/*
 * Driver for Texas Instruments INA219, INA226 power monitor chips
 *
 * INA219:
 * Zero Drift Bi-Directional Current/Power Monitor with I2C Interface
 * Datasheet: http://www.ti.com/product/ina219
 *
 * INA220:
 * Bi-Directional Current/Power Monitor with I2C Interface
 * Datasheet: http://www.ti.com/product/ina220
 *
 * INA226:
 * Bi-Directional Current/Power Monitor with I2C Interface
 * Datasheet: http://www.ti.com/product/ina226
 *
 * INA230:
 * Bi-directional Current/Power Monitor with I2C Interface
 * Datasheet: http://www.ti.com/product/ina230
 *
 * Copyright (C) 2012 Lothar Felten <l-felten@ti.com>
 * Thanks to Jan Volkering
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/jiffies.h>
#include <linux/of.h>
#include <linux/delay.h>
#include <linux/util_macros.h>
#include <linux/regmap.h>

#include <linux/platform_data/ina2xx.h>

/* common register definitions */

#define INA2XX_CONFIG			0x00

#define INA2XX_SHUNT_VOLTAGE		0x01 
/* readonly */

#define INA2XX_BUS_VOLTAGE		0x02 
/* readonly */

#define INA2XX_POWER			0x03 
/* readonly */

#define INA2XX_CURRENT			0x04 
/* readonly */

#define INA2XX_CALIBRATION		0x05

/* INA226 register definitions */

#define INA226_MASK_ENABLE		0x06

#define INA226_ALERT_LIMIT		0x07

#define INA226_DIE_ID			0xFF

/* register count */

#define INA219_REGISTERS		6

#define INA226_REGISTERS		8


#define INA2XX_MAX_REGISTERS		8

/* settings - depend on use case */

#define INA219_CONFIG_DEFAULT		0x399F	
/* PGA=8 */

#define INA226_CONFIG_DEFAULT		0x4527	
/* averages=16 */

/* 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 };


struct ina2xx_config {
	
u16 config_default;
	
int calibration_factor;
	
int registers;
	
int shunt_div;
	
int bus_voltage_shift;
	
int bus_voltage_lsb;	/* uV */
	
int power_lsb;		/* uW */
};


struct ina2xx_data {
	
const struct ina2xx_config *config;

	
long rshunt;
	
struct mutex config_lock;
	
struct regmap *regmap;

	
const struct attribute_group *groups[INA2XX_MAX_ATTRIBUTE_GROUPS];
};


static const struct ina2xx_config ina2xx_config[] = {
	[ina219] = {
		.config_default = INA219_CONFIG_DEFAULT,
		.calibration_factor = 40960000,
		.registers = INA219_REGISTERS,
		.shunt_div = 100,
		.bus_voltage_shift = 3,
		.bus_voltage_lsb = 4000,
		.power_lsb = 20000,
        },
	[ina226] = {
		.config_default = INA226_CONFIG_DEFAULT,
		.calibration_factor = 5120000,
		.registers = INA226_REGISTERS,
		.shunt_div = 400,
		.bus_voltage_shift = 0,
		.bus_voltage_lsb = 1250,
		.power_lsb = 25000,
        },
};

/*
 * 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); }

Contributors

PersonTokensPropCommitsCommitProp
Bartosz Golaszewski31100.00%1100.00%
Total31100.00%1100.00%

/* * 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); }

Contributors

PersonTokensPropCommitsCommitProp
Bartosz Golaszewski45100.00%2100.00%
Total45100.00%2100.00%


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); }

Contributors

PersonTokensPropCommitsCommitProp
Bartosz Golaszewski3794.87%266.67%
Marc Titinger25.13%133.33%
Total39100.00%3100.00%

/* * Initialize the configuration and calibration registers. */
static int ina2xx_init(struct ina2xx_data *data) { int ret = regmap_write(data->regmap, INA2XX_CONFIG, data->config->config_default); if (ret < 0) return ret; /* * Set current LSB to 1mA, shunt is in uOhms * (equation 13 in datasheet). */ return ina2xx_calibrate(data); }

Contributors

PersonTokensPropCommitsCommitProp
Bartosz Golaszewski2146.67%240.00%
Felten, Lothar1328.89%120.00%
Marc Titinger920.00%120.00%
Guenter Roeck24.44%120.00%
Total45100.00%5100.00%


static int ina2xx_read_reg(struct device *dev, int reg, unsigned int *regval) { struct ina2xx_data *data = dev_get_drvdata(dev); int ret, retry; dev_dbg(dev, "Starting register %d read\n", reg); for (retry = 5; retry; retry--) { 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; } } return 0; } /* * 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; }

Contributors

PersonTokensPropCommitsCommitProp
Marc Titinger7943.89%125.00%
Bartosz Golaszewski7340.56%250.00%
Felten, Lothar2815.56%125.00%
Total180100.00%4100.00%


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)regval, data->config->shunt_div); break; case INA2XX_BUS_VOLTAGE: val = (regval >> data->config->bus_voltage_shift) * data->config->bus_voltage_lsb; val = DIV_ROUND_CLOSEST(val, 1000); break; case INA2XX_POWER: val = regval * data->config->power_lsb; break; case INA2XX_CURRENT: /* signed register, LSB=1mA (selected), in mA */ val = (s16)regval; break; case INA2XX_CALIBRATION: val = DIV_ROUND_CLOSEST(data->config->calibration_factor, regval); break; default: /* programmer goofed */ WARN_ON_ONCE(1); val = 0; break; } return val; }

Contributors

PersonTokensPropCommitsCommitProp
Felten, Lothar7655.47%116.67%
Guenter Roeck2820.44%116.67%
Bartosz Golaszewski1611.68%233.33%
Marc Titinger96.57%116.67%
Fabio Baltieri85.84%116.67%
Total137100.00%6100.00%


static ssize_t ina2xx_show_value(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); unsigned int regval; int err = ina2xx_read_reg(dev, attr->index, &regval); if (err < 0) return err; return snprintf(buf, PAGE_SIZE, "%d\n", ina2xx_get_value(data, attr->index, regval)); }

Contributors

PersonTokensPropCommitsCommitProp
Felten, Lothar5561.80%133.33%
Marc Titinger2629.21%133.33%
Guenter Roeck88.99%133.33%
Total89100.00%3100.00%


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; }

Contributors

PersonTokensPropCommitsCommitProp
Bartosz Golaszewski11392.62%150.00%
Marc Titinger97.38%150.00%
Total122100.00%2100.00%


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; }

Contributors

PersonTokensPropCommitsCommitProp
Bartosz Golaszewski9892.45%133.33%
Marc Titinger65.66%133.33%
Felten, Lothar21.89%133.33%
Total106100.00%3100.00%


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, &regval); if (status) return status; return snprintf(buf, PAGE_SIZE, "%d\n", ina226_reg_to_interval(regval)); }

Contributors

PersonTokensPropCommitsCommitProp
Bartosz Golaszewski4967.12%150.00%
Marc Titinger2432.88%150.00%
Total73100.00%2100.00%

/* shunt voltage */ static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, ina2xx_show_value, NULL, INA2XX_SHUNT_VOLTAGE); /* bus voltage */ static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, ina2xx_show_value, NULL, INA2XX_BUS_VOLTAGE); /* calculated current */ static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, ina2xx_show_value, NULL, INA2XX_CURRENT); /* calculated power */ 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_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_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 device *dev = &client->dev; struct ina2xx_data *data; struct device *hwmon_dev; u32 val; int ret, group = 0; data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; /* 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 (val <= 0 || val > data->config->calibration_factor) return -ENODEV; data->rshunt = val; ina2xx_regmap_config.max_register = data->config->registers; 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); } ret = ina2xx_init(data); if (ret < 0) { dev_err(dev, "error configuring the device: %d\n", ret); return -ENODEV; } mutex_init(&data->config_lock); 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); dev_info(dev, "power monitor %s (Rshunt = %li uOhm)\n", id->name, data->rshunt); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Bartosz Golaszewski9129.07%436.36%
Marc Titinger8226.20%327.27%
Felten, Lothar8125.88%19.09%
Guenter Roeck4213.42%218.18%
Tang Yuantian175.43%19.09%
Total313100.00%11100.00%

static const struct i2c_device_id ina2xx_id[] = { { "ina219", ina219 }, { "ina220", ina219 }, { "ina226", ina226 }, { "ina230", ina226 }, { "ina231", ina226 }, { } }; MODULE_DEVICE_TABLE(i2c, ina2xx_id); static struct i2c_driver ina2xx_driver = { .driver = { .name = "ina2xx", }, .probe = ina2xx_probe, .id_table = ina2xx_id, }; module_i2c_driver(ina2xx_driver); MODULE_AUTHOR("Lothar Felten <l-felten@ti.com>"); MODULE_DESCRIPTION("ina2xx driver"); MODULE_LICENSE("GPL");

Overall Contributors

PersonTokensPropCommitsCommitProp
Bartosz Golaszewski74941.27%735.00%
Felten, Lothar53329.37%15.00%
Marc Titinger27214.99%315.00%
Guenter Roeck22312.29%420.00%
Tang Yuantian201.10%15.00%
Fabio Baltieri80.44%15.00%
Kevin Hilman60.33%15.00%
Jean Delvare30.17%15.00%
Wei Yongjun10.06%15.00%
Total1815100.00%20100.00%
Directory: drivers/hwmon
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with cregit.