/* * ina219.c - driver for TI INA219 current / power monitor sensor * * Copyright (c) 2011-2013, NVIDIA Corporation. * * The INA219 is a sensor chip made by Texas Instruments. It measures * power, voltage and current on a power rail. * Complete datasheet can be obtained from website: * http://focus.ti.com/lit/ds/symlink/ina219.pdf * * 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. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "linux/ina219.h" #include #include #include #define DRIVER_NAME "ina219" /* INA219 register offsets */ #define INA219_CONFIG 0 #define INA219_SHUNT 1 #define INA219_VOLTAGE 2 #define INA219_POWER 3 #define INA219_CURRENT 4 #define INA219_CAL 5 #define INA219_RESET 0x8000 #define busv_register_to_mv(x) (((x) >> 3) * 4) #define shuntv_register_to_uv(x) ((x) * 10) struct power_mon_data { s32 voltage; s32 currentInMillis; s32 power; }; struct ina219_data { struct device *hwmon_dev; struct i2c_client *client; struct ina219_platform_data *pInfo; struct power_mon_data pm_data; struct mutex mutex; int state; }; #define STOPPED 0 #define RUNNING 1 /* Set non-zero to enable debug prints */ #define INA219_DEBUG_PRINTS 0 #if INA219_DEBUG_PRINTS #define DEBUG_INA219(x) printk x #else #define DEBUG_INA219(x) #endif static s32 power_down_INA219(struct i2c_client *client) { s32 retval; retval = i2c_smbus_write_word_data(client, INA219_CONFIG, 0); if (retval < 0) dev_err(&client->dev, "power down failure sts: 0x%x\n", retval); return retval; } static s32 power_up_INA219(struct i2c_client *client, u16 config_data) { s32 retval; struct ina219_data *data = i2c_get_clientdata(client); retval = i2c_smbus_write_word_data(client, INA219_CONFIG, __constant_cpu_to_be16(config_data)); if (retval < 0) goto error; retval = i2c_smbus_write_word_data(client, INA219_CAL, __constant_cpu_to_be16(data->pInfo->calibration_data)); if (retval < 0) goto error; return 0; error: dev_err(&client->dev, "power up failure sts: 0x%x\n", retval); return retval; } static s32 show_rail_name(struct device *dev, struct device_attribute *attr, char *buf) { struct i2c_client *client = to_i2c_client(dev); struct ina219_data *data = i2c_get_clientdata(client); return sprintf(buf, "%s\n", data->pInfo->rail_name); } static s32 show_voltage(struct device *dev, struct device_attribute *attr, char *buf) { struct i2c_client *client = to_i2c_client(dev); struct ina219_data *data = i2c_get_clientdata(client); s32 voltage_mV; int cur_state; mutex_lock(&data->mutex); cur_state = data->state; if (data->state == STOPPED) if (power_up_INA219(client, data->pInfo->trig_conf) < 0) goto error; /* getting voltage readings in milli volts*/ voltage_mV = (s16)be16_to_cpu(i2c_smbus_read_word_data(client, INA219_VOLTAGE)); DEBUG_INA219(("Ina219 voltage reg Value: 0x%x\n", voltage_mV)); if (voltage_mV < 0) goto error; voltage_mV = busv_register_to_mv(voltage_mV); DEBUG_INA219(("Ina219 voltage in mv: %d\n", voltage_mV)); DEBUG_INA219(("%s volt = %d\n", __func__, voltage_mV)); if (cur_state == STOPPED) if (power_down_INA219(client) < 0) goto error; mutex_unlock(&data->mutex); return sprintf(buf, "%d mV\n", voltage_mV); error: mutex_unlock(&data->mutex); dev_err(dev, "%s: failed\n", __func__); return -EAGAIN; } static s32 show_shunt_voltage(struct device *dev, struct device_attribute *attr, char *buf) { struct i2c_client *client = to_i2c_client(dev); struct ina219_data *data = i2c_get_clientdata(client); s32 voltage_uV; int cur_state; mutex_lock(&data->mutex); cur_state = data->state; if (data->state == STOPPED) if (power_up_INA219(client, data->pInfo->trig_conf) < 0) goto error; /* getting voltage readings in milli volts*/ voltage_uV = be16_to_cpu(i2c_smbus_read_word_data(client, INA219_SHUNT)); DEBUG_INA219(("Ina219 voltage reg Value: 0x%x\n", voltage_uV)); if (voltage_uV < 0) goto error; voltage_uV = shuntv_register_to_uv(voltage_uV); if (cur_state == STOPPED) if (power_down_INA219(client) < 0) goto error; mutex_unlock(&data->mutex); DEBUG_INA219(("%s volt = %d\n", __func__, voltage_uV)); return sprintf(buf, "%d uV\n", voltage_uV); error: mutex_unlock(&data->mutex); dev_err(dev, "%s: failed\n", __func__); return -EAGAIN; } static s32 show_power2(struct device *dev, struct device_attribute *attr, char *buf) { struct i2c_client *client = to_i2c_client(dev); struct ina219_data *data = i2c_get_clientdata(client); s32 power_mW; s32 voltage_shunt_uV; s32 voltage_bus_mV; s32 inverse_shunt_resistor; int cur_state; #if INA219_DEBUG_PRINTS s32 power_raw; #endif mutex_lock(&data->mutex); cur_state = data->state; if (data->state == STOPPED) if (power_up_INA219(client, data->pInfo->trig_conf) < 0) goto error; voltage_shunt_uV = be16_to_cpu(i2c_smbus_read_word_data(client, INA219_SHUNT)); if (voltage_shunt_uV < 0) goto error; voltage_shunt_uV = shuntv_register_to_uv(voltage_shunt_uV); voltage_bus_mV = be16_to_cpu(i2c_smbus_read_word_data(client, INA219_VOLTAGE)); if (voltage_bus_mV < 0) goto error; voltage_bus_mV = busv_register_to_mv(voltage_bus_mV); /*avoid overflow*/ inverse_shunt_resistor = 1000/(data->pInfo->shunt_resistor); power_mW = voltage_shunt_uV * inverse_shunt_resistor; /*current uAmps*/ power_mW = power_mW / 1000; /*current mAmps*/ power_mW = power_mW * (voltage_bus_mV); /*Power uW*/ power_mW = power_mW / 1000; /*Power mW*/ #if INA219_DEBUG_PRINTS power_raw = be16_to_cpu(i2c_smbus_read_word_data(client, INA219_POWER)); power_raw *= data->pInfo->power_lsb; power_raw /= data->pInfo->precision_multiplier; DEBUG_INA219(("INA219: power_mW: %d, power_raw:%d\n", power_mW, power_raw)); #endif if (cur_state == STOPPED) if (power_down_INA219(client) < 0) goto error; mutex_unlock(&data->mutex); DEBUG_INA219(("%s pow = %d\n", __func__, power_mW)); return sprintf(buf, "%d mW\n", power_mW); error: mutex_unlock(&data->mutex); dev_err(dev, "%s: failed\n", __func__); return -EAGAIN; } /*This function is kept to support INA219 on cardhu*/ static s32 show_power(struct device *dev, struct device_attribute *attr, char *buf) { struct i2c_client *client = to_i2c_client(dev); struct ina219_data *data = i2c_get_clientdata(client); s32 retval; s32 power_mW; s32 voltage_mV; s32 overflow, conversion; int cur_state; mutex_lock(&data->mutex); cur_state = data->state; if (data->state == STOPPED) { if (power_up_INA219(client, data->pInfo->trig_conf) < 0) { retval = -EAGAIN; goto error; } } else { mutex_unlock(&data->mutex); return show_power2(dev, attr, buf); } /* check if the readings are valid */ do { /* read power register to clear conversion bit */ retval = be16_to_cpu(i2c_smbus_read_word_data(client, INA219_POWER)); if (retval < 0) { dev_err(dev, "CNVR bit clearing failure sts: 0x%x\n", retval); goto error; } voltage_mV = be16_to_cpu(i2c_smbus_read_word_data(client, INA219_VOLTAGE)); DEBUG_INA219(("Ina219 voltage reg Value: 0x%x\n", voltage_mV)); overflow = voltage_mV & 1; if (overflow) { dev_err(dev, "overflow error\n"); goto error; } conversion = (voltage_mV >> 1) & 1; DEBUG_INA219(("\n ina219 CNVR value:%d", conversion)); } while (!conversion); /* getting power readings in milli watts*/ power_mW = be16_to_cpu(i2c_smbus_read_word_data(client, INA219_POWER)); DEBUG_INA219(("Ina219 power Reg: 0x%x\n", power_mW)); power_mW *= data->pInfo->power_lsb; if (data->pInfo->precision_multiplier) power_mW /= data->pInfo->precision_multiplier; DEBUG_INA219(("Ina219 power Val: %d\n", power_mW)); if (power_mW < 0) goto error; /* set ina219 to power down mode */ retval = power_down_INA219(client); if (retval < 0) goto error; mutex_unlock(&data->mutex); DEBUG_INA219(("%s pow = %d\n", __func__, power_mW)); return sprintf(buf, "%d mW\n", power_mW); error: mutex_unlock(&data->mutex); dev_err(dev, "%s: failed\n", __func__); return retval; } static s32 show_current2(struct device *dev, struct device_attribute *attr, char *buf) { struct i2c_client *client = to_i2c_client(dev); struct ina219_data *data = i2c_get_clientdata(client); s32 current_mA; s32 voltage_uV; s32 inverse_shunt_resistor; int cur_state; #if INA219_DEBUG_PRINTS s32 current_raw; #endif mutex_lock(&data->mutex); cur_state = data->state; if (data->state == STOPPED) if (power_up_INA219(client, data->pInfo->trig_conf) < 0) goto error; voltage_uV = (s16)be16_to_cpu(i2c_smbus_read_word_data(client, INA219_SHUNT)); if (voltage_uV < 0) goto error; inverse_shunt_resistor = 1000/(data->pInfo->shunt_resistor); voltage_uV = shuntv_register_to_uv(voltage_uV); current_mA = voltage_uV * inverse_shunt_resistor; current_mA = current_mA / 1000; #if INA219_DEBUG_PRINTS current_raw = (s16)be16_to_cpu(i2c_smbus_read_word_data(client, INA219_CURRENT)); current_raw *= data->pInfo->power_lsb; current_raw /= data->pInfo->divisor; current_raw /= data->pInfo->precision_multiplier; DEBUG_INA219(("%s current = %d current_raw=%d\n", __func__, current_mA, current_raw)); #endif if (cur_state == STOPPED) if (power_down_INA219(client) < 0) goto error; mutex_unlock(&data->mutex); return sprintf(buf, "%d mA\n", current_mA); error: dev_err(dev, "%s: failed\n", __func__); mutex_unlock(&data->mutex); return -EAGAIN; } /*This function is kept to support INA219 on cardhu*/ static s32 show_current(struct device *dev, struct device_attribute *attr, char *buf) { struct i2c_client *client = to_i2c_client(dev); struct ina219_data *data = i2c_get_clientdata(client); s32 retval; s32 current_mA; s32 voltage_mV; s32 overflow, conversion; int cur_state; mutex_lock(&data->mutex); cur_state = data->state; if (data->state == STOPPED) { if (power_up_INA219(client, data->pInfo->trig_conf) < 0) { retval = -EAGAIN; goto error; } } else { mutex_unlock(&data->mutex); show_current2(dev, attr, buf); } /* check if the readings are valid */ do { /* read power register to clear conversion bit */ retval = be16_to_cpu(i2c_smbus_read_word_data(client, INA219_POWER)); if (retval < 0) { dev_err(dev, "CNVR bit clearing failure sts: 0x%x\n", retval); goto error; } voltage_mV = be16_to_cpu(i2c_smbus_read_word_data(client, INA219_VOLTAGE)); DEBUG_INA219(("Ina219 voltage reg Value: 0x%x\n", voltage_mV)); overflow = voltage_mV & 1; if (overflow) { dev_err(dev, "overflow error\n"); goto error; } conversion = (voltage_mV >> 1) & 1; DEBUG_INA219(("\n ina219 CNVR value:%d", conversion)); } while (!conversion); /* getting current readings in milli amps*/ current_mA = be16_to_cpu(i2c_smbus_read_word_data(client, INA219_CURRENT)); DEBUG_INA219(("Ina219 current Reg: 0x%x\n", current_mA)); if (current_mA < 0) goto error; current_mA = (current_mA * data->pInfo->power_lsb) / data->pInfo->divisor; if (data->pInfo->precision_multiplier) current_mA /= data->pInfo->precision_multiplier; DEBUG_INA219(("Ina219 current Value: %d\n", current_mA)); if (cur_state == STOPPED) if (power_down_INA219(client) < 0) goto error; mutex_unlock(&data->mutex); DEBUG_INA219(("%s current = %d\n", __func__, current_mA)); return sprintf(buf, "%d mA\n", current_mA); error: mutex_unlock(&data->mutex); dev_err(dev, "%s: failed\n", __func__); return retval; } static int ina219_state_show(struct device *dev, struct device_attribute *attr, char *buf) { struct i2c_client *client = to_i2c_client(dev); struct ina219_data *data = i2c_get_clientdata(client); int count; mutex_lock(&data->mutex); count = sprintf(buf, "%d\n", data->state); mutex_unlock(&data->mutex); return count; } static int ina219_state_set(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct i2c_client *client = to_i2c_client(dev); struct ina219_data *data = i2c_get_clientdata(client); int retval = -1; long new; retval = kstrtol(buf, 10, &new); if (retval < 0 || new > INT_MAX || new < INT_MIN) return -EINVAL; mutex_lock(&data->mutex); if ((new > 0) && (data->state == STOPPED)) retval = power_up_INA219(client, data->pInfo->cont_conf); else if ((new == 0) && (data->state == RUNNING)) retval = power_down_INA219(client); if (retval < 0) { dev_err(dev, "Error in switching INA on/off!"); mutex_unlock(&data->mutex); return -EAGAIN; } if (new) data->state = RUNNING; else data->state = STOPPED; mutex_unlock(&data->mutex); return 1; } static struct sensor_device_attribute ina219[] = { SENSOR_ATTR(shunt_voltage, S_IRUGO, show_shunt_voltage, NULL, 0), SENSOR_ATTR(rail_name, S_IRUGO, show_rail_name, NULL, 0), SENSOR_ATTR(in1_input, S_IRUGO, show_voltage, NULL, 0), SENSOR_ATTR(curr1_input, S_IRUGO, show_current, NULL, 0), SENSOR_ATTR(curr2_input, S_IRUGO, show_current2, NULL, 0), SENSOR_ATTR(power1_input, S_IRUGO, show_power, NULL, 0), SENSOR_ATTR(power2_input, S_IRUGO, show_power2, NULL, 0), SENSOR_ATTR(cur_state, 0644, ina219_state_show, ina219_state_set, 0), }; static int ina219_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct ina219_data *data; int err; u8 i; data = kzalloc(sizeof(struct ina219_data), GFP_KERNEL); if (!data) { err = -ENOMEM; goto exit; } i2c_set_clientdata(client, data); data->pInfo = client->dev.platform_data; mutex_init(&data->mutex); data->state = STOPPED; /* reset ina219 */ err = i2c_smbus_write_word_data(client, INA219_CONFIG, __constant_cpu_to_be16(INA219_RESET)); if (err < 0) { dev_err(&client->dev, "ina219 reset failure status: 0x%x\n", err); goto exit_free; } for (i = 0; i < ARRAY_SIZE(ina219); i++) { err = device_create_file(&client->dev, &ina219[i].dev_attr); if (err) { dev_err(&client->dev, "device_create_file failed.\n"); goto exit_free; } } data->hwmon_dev = hwmon_device_register(&client->dev); if (IS_ERR(data->hwmon_dev)) { err = PTR_ERR(data->hwmon_dev); goto exit_remove; } err = power_down_INA219(client); if (err < 0) { dev_err(&client->dev, "ina219 power-down failure status: 0x%x\n", err); goto exit_remove; } return 0; exit_remove: for (i = 0; i < ARRAY_SIZE(ina219); i++) device_remove_file(&client->dev, &ina219[i].dev_attr); exit_free: kfree(data); exit: return err; } static int ina219_remove(struct i2c_client *client) { u8 i; struct ina219_data *data = i2c_get_clientdata(client); mutex_lock(&data->mutex); power_down_INA219(client); data->state = STOPPED; mutex_unlock(&data->mutex); hwmon_device_unregister(data->hwmon_dev); for (i = 0; i < ARRAY_SIZE(ina219); i++) device_remove_file(&client->dev, &ina219[i].dev_attr); kfree(data); return 0; } static const struct i2c_device_id ina219_id[] = { {DRIVER_NAME, 0 }, {} }; MODULE_DEVICE_TABLE(i2c, ina219_id); static struct i2c_driver ina219_driver = { .class = I2C_CLASS_HWMON, .driver = { .name = DRIVER_NAME, }, .probe = ina219_probe, .remove = ina219_remove, .id_table = ina219_id, }; static int __init ina219_init(void) { return i2c_add_driver(&ina219_driver); } static void __exit ina219_exit(void) { i2c_del_driver(&ina219_driver); } module_init(ina219_init); module_exit(ina219_exit); MODULE_LICENSE("GPL");