From 748e4f3d702def1a4bff191e0cf93b6a05340f01 Mon Sep 17 00:00:00 2001 From: hc <hc@nodka.com> Date: Fri, 10 May 2024 07:41:34 +0000 Subject: [PATCH] add gpio led uart --- kernel/drivers/hwmon/ina2xx.c | 245 +++++++++++++++++++++++++++++++++++++++++------- 1 files changed, 209 insertions(+), 36 deletions(-) diff --git a/kernel/drivers/hwmon/ina2xx.c b/kernel/drivers/hwmon/ina2xx.c index 07ee195..ca97f9e 100644 --- a/kernel/drivers/hwmon/ina2xx.c +++ b/kernel/drivers/hwmon/ina2xx.c @@ -1,28 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * 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 + * Datasheet: https://www.ti.com/product/ina219 * * INA220: * Bi-Directional Current/Power Monitor with I2C Interface - * Datasheet: http://www.ti.com/product/ina220 + * Datasheet: https://www.ti.com/product/ina220 * * INA226: * Bi-Directional Current/Power Monitor with I2C Interface - * Datasheet: http://www.ti.com/product/ina226 + * Datasheet: https://www.ti.com/product/ina226 * * INA230: * Bi-directional Current/Power Monitor with I2C Interface - * Datasheet: http://www.ti.com/product/ina230 + * Datasheet: https://www.ti.com/product/ina230 * * Copyright (C) 2012 Lothar Felten <lothar.felten@gmail.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> @@ -76,6 +73,17 @@ #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_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 + +/* 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 @@ -140,7 +148,7 @@ * 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. + * https://www.ti.com/lit/ds/symlink/ina226.pdf. */ static const int ina226_avg_tab[] = { 1, 4, 16, 64, 128, 256, 512, 1024 }; @@ -290,7 +298,7 @@ return val; } -static ssize_t ina2xx_show_value(struct device *dev, +static ssize_t ina2xx_value_show(struct device *dev, struct device_attribute *da, char *buf) { struct sensor_device_attribute *attr = to_sensor_dev_attr(da); @@ -304,6 +312,145 @@ return snprintf(buf, PAGE_SIZE, "%d\n", 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 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; + + /* + * 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, attr->index, val)); + if (ret < 0) + goto abort; + + if (val != 0) { + ret = regmap_update_bits(data->regmap, INA226_MASK_ENABLE, + INA226_ALERT_CONFIG_MASK, + BIT(attr->index)); + if (ret < 0) + goto abort; + } + + ret = count; +abort: + mutex_unlock(&data->config_lock); + 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); } /* @@ -329,16 +476,15 @@ return 0; } -static ssize_t ina2xx_show_shunt(struct device *dev, - struct device_attribute *da, - char *buf) +static ssize_t ina2xx_shunt_show(struct device *dev, + struct device_attribute *da, char *buf) { struct ina2xx_data *data = dev_get_drvdata(dev); return snprintf(buf, PAGE_SIZE, "%li\n", data->rshunt); } -static ssize_t ina2xx_store_shunt(struct device *dev, +static ssize_t ina2xx_shunt_store(struct device *dev, struct device_attribute *da, const char *buf, size_t count) { @@ -356,9 +502,9 @@ return count; } -static ssize_t ina226_set_interval(struct device *dev, - struct device_attribute *da, - const char *buf, size_t count) +static ssize_t ina226_interval_store(struct device *dev, + struct device_attribute *da, + const char *buf, size_t count) { struct ina2xx_data *data = dev_get_drvdata(dev); unsigned long val; @@ -380,7 +526,7 @@ return count; } -static ssize_t ina226_show_interval(struct device *dev, +static ssize_t ina226_interval_show(struct device *dev, struct device_attribute *da, char *buf) { struct ina2xx_data *data = dev_get_drvdata(dev); @@ -395,29 +541,45 @@ } /* shunt voltage */ -static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, ina2xx_show_value, NULL, - INA2XX_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(in1_input, S_IRUGO, ina2xx_show_value, NULL, - INA2XX_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(curr1_input, S_IRUGO, ina2xx_show_value, NULL, - INA2XX_CURRENT); +static SENSOR_DEVICE_ATTR_RO(curr1_input, ina2xx_value, INA2XX_CURRENT); /* calculated power */ -static SENSOR_DEVICE_ATTR(power1_input, S_IRUGO, ina2xx_show_value, NULL, - INA2XX_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(shunt_resistor, S_IRUGO | S_IWUSR, - ina2xx_show_shunt, ina2xx_store_shunt, - INA2XX_CALIBRATION); +static SENSOR_DEVICE_ATTR_RW(shunt_resistor, ina2xx_shunt, INA2XX_CALIBRATION); /* update interval (ina226 only) */ -static SENSOR_DEVICE_ATTR(update_interval, S_IRUGO | S_IWUSR, - ina226_show_interval, ina226_set_interval, 0); +static SENSOR_DEVICE_ATTR_RW(update_interval, ina226_interval, 0); /* pointers to created device attributes */ static struct attribute *ina2xx_attrs[] = { @@ -434,6 +596,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, }; @@ -442,8 +614,9 @@ .attrs = ina226_attrs, }; -static int ina2xx_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static const struct i2c_device_id ina2xx_id[]; + +static int ina2xx_probe(struct i2c_client *client) { struct device *dev = &client->dev; struct ina2xx_data *data; @@ -455,7 +628,7 @@ if (client->dev.of_node) chip = (enum ina2xx_ids)of_device_get_match_data(&client->dev); else - chip = id->driver_data; + chip = i2c_match_id(ina2xx_id, client)->driver_data; data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); if (!data) @@ -515,7 +688,7 @@ }; MODULE_DEVICE_TABLE(i2c, ina2xx_id); -static const struct of_device_id ina2xx_of_match[] = { +static const struct of_device_id __maybe_unused ina2xx_of_match[] = { { .compatible = "ti,ina219", .data = (void *)ina219 @@ -545,7 +718,7 @@ .name = "ina2xx", .of_match_table = of_match_ptr(ina2xx_of_match), }, - .probe = ina2xx_probe, + .probe_new = ina2xx_probe, .id_table = ina2xx_id, }; -- Gitblit v1.6.2