As long as somebody is regularly accessing axp20x hwmon sysfs entries,
maintain a work queue for 25Hz periodic sampling of ACIN voltage/current
information. And calculate moving average using this data. This periodic
sampling stops after ~1 minute from the last access to axp20x hwmon sysfs.
Comparison of the current draw (in mA) for different workloads, measured
by the axp20x hwmon averaged stats (on the left side) and with a multimeter
(on the right side). The following sunxi based hardware has been tested
(without anything extra connected to USB/SATA):
Olinuxino-Lime (Allwinner A10):
300 315
555 590
990 1060
Cubietruck (Allwinner A20):
215 240
310 345
730 800
Cubieboard2 (Allwinner A20):
80 245
180 415
325 610
490 820
Mele A2000 (Allwinner A10):
180 450
280 560
450 770
AXP209 on Olinuxino-Lime and Cubietruck reports reasonably accurate
results. But not so much for the other devices.
v2: The decision whether to run periodic sampling is now fully
automatic. No configuration is needed. And no parasitic background
activity is expected when nobody is interested in axp20x hwmon
sysfs data.
Signed-off-by: Siarhei Siamashka <siarhei.siamas...@gmail.com>
---
drivers/power/axp_power/axp-mfd.c | 4 +++
drivers/power/axp_power/axp20-mfd.h | 68 +++++++++++++++++++++++++++++++++----
include/linux/mfd/axp-mfd.h | 3 ++
3 files changed, 69 insertions(+), 6 deletions(-)
diff --git a/drivers/power/axp_power/axp-mfd.c
b/drivers/power/axp_power/axp-mfd.c
index cfa894a..6fd1264 100644
--- a/drivers/power/axp_power/axp-mfd.c
+++ b/drivers/power/axp_power/axp-mfd.c
@@ -365,6 +365,10 @@ static int __devexit axp_mfd_remove(struct i2c_client
*client)
#ifdef CONFIG_AXP_HWMON
if (chip->itm_enabled == 1) {
+ cancel_delayed_work(&axp_hwmon_work);
+ flush_workqueue(wq);
+ destroy_workqueue(wq);
+
hwmon_device_unregister(chip->hwmon_dev);
sysfs_remove_group(&client->dev.kobj, &axp20_group);
}
diff --git a/drivers/power/axp_power/axp20-mfd.h
b/drivers/power/axp_power/axp20-mfd.h
index 4244d49..fe7aeb1 100644
--- a/drivers/power/axp_power/axp20-mfd.h
+++ b/drivers/power/axp_power/axp20-mfd.h
@@ -28,13 +28,14 @@
#include <linux/hwmon.h>
#include <linux/err.h>
-static struct axp_mfd_chip *axp20_update_device(struct device *dev);
+static struct
+axp_mfd_chip *axp20_update_device(struct device *dev, int from_wq);
static ssize_t
show_temp(struct device *dev, struct device_attribute *devattr, char *buf)
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
- struct axp_mfd_chip *data = axp20_update_device(dev);
+ struct axp_mfd_chip *data = axp20_update_device(dev, 0);
if (attr->index == 1)
return sprintf(buf, "264800\n");
if (attr->index == 2)
@@ -49,9 +50,11 @@ show_acin_voltage(struct device *dev, struct
device_attribute *devattr,
char *buf)
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
- struct axp_mfd_chip *data = axp20_update_device(dev);
+ struct axp_mfd_chip *data = axp20_update_device(dev, 0);
if (attr->index == 3)
return sprintf(buf, "ACIN voltage\n");
+ if (attr->index == 4)
+ return sprintf(buf, "%d\n", data->acin_avg_voltage / 64);
return sprintf(buf, "%d\n", data->acin_voltage);
}
@@ -60,9 +63,11 @@ show_acin_current(struct device *dev, struct
device_attribute *devattr,
char *buf)
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
- struct axp_mfd_chip *data = axp20_update_device(dev);
+ struct axp_mfd_chip *data = axp20_update_device(dev, 0);
if (attr->index == 3)
return sprintf(buf, "ACIN current\n");
+ if (attr->index == 4)
+ return sprintf(buf, "%d\n", data->acin_avg_current / 64);
return sprintf(buf, "%d\n", data->acin_current);
}
@@ -70,9 +75,11 @@ static ssize_t
show_acin_power(struct device *dev, struct device_attribute *devattr, char
*buf)
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
- struct axp_mfd_chip *data = axp20_update_device(dev);
+ struct axp_mfd_chip *data = axp20_update_device(dev, 0);
if (attr->index == 3)
return sprintf(buf, "ACIN power\n");
+ if (attr->index == 4)
+ return sprintf(buf, "%d\n", data->acin_avg_power / 64);
return sprintf(buf, "%d\n", data->acin_power);
}
@@ -82,10 +89,13 @@ static SENSOR_DEVICE_ATTR(temp1_min, S_IRUGO, show_temp,
NULL, 2);
static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_temp, NULL, 3);
static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, show_acin_voltage, NULL, 0);
static SENSOR_DEVICE_ATTR(in0_label, S_IRUGO, show_acin_voltage, NULL, 3);
+static SENSOR_DEVICE_ATTR(in0_average, S_IRUGO, show_acin_voltage, NULL, 4);
static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, show_acin_current, NULL, 0);
static SENSOR_DEVICE_ATTR(curr1_label, S_IRUGO, show_acin_current, NULL, 3);
+static SENSOR_DEVICE_ATTR(curr1_average, S_IRUGO, show_acin_current, NULL, 4);
static SENSOR_DEVICE_ATTR(power1_input, S_IRUGO, show_acin_power, NULL, 0);
static SENSOR_DEVICE_ATTR(power1_label, S_IRUGO, show_acin_power, NULL, 3);
+static SENSOR_DEVICE_ATTR(power1_average, S_IRUGO, show_acin_power, NULL, 4);
static struct attribute *axp20_attributes[] = {
&sensor_dev_attr_temp1_input.dev_attr.attr,
@@ -94,10 +104,13 @@ static struct attribute *axp20_attributes[] = {
&sensor_dev_attr_temp1_label.dev_attr.attr,
&sensor_dev_attr_in0_input.dev_attr.attr,
&sensor_dev_attr_in0_label.dev_attr.attr,
+ &sensor_dev_attr_in0_average.dev_attr.attr,
&sensor_dev_attr_curr1_input.dev_attr.attr,
&sensor_dev_attr_curr1_label.dev_attr.attr,
+ &sensor_dev_attr_curr1_average.dev_attr.attr,
&sensor_dev_attr_power1_input.dev_attr.attr,
&sensor_dev_attr_power1_label.dev_attr.attr,
+ &sensor_dev_attr_power1_average.dev_attr.attr,
NULL
};
@@ -125,10 +138,23 @@ static int axp_read_adc(struct device *dev, struct
i2c_client *client, int reg)
return (high << 4) + (low & 0x0F);
}
+static int axp_hwmon_wq_counter;
+
+static void axp_hwmon_work_handler(struct work_struct *w);
+static struct workqueue_struct *wq;
+static DECLARE_DELAYED_WORK(axp_hwmon_work, axp_hwmon_work_handler);
+
+static struct device *wq_dev_arg;
+
+static void axp_hwmon_work_handler(struct work_struct *w)
+{
+ axp20_update_device(wq_dev_arg, 1);
+}
+
/*
* * function that update the status of the chips (temperature)
* */
-static struct axp_mfd_chip *axp20_update_device(struct device *dev)
+static struct axp_mfd_chip *axp20_update_device(struct device *dev, int
from_wq)
{
struct i2c_client *client = to_i2c_client(dev);
struct axp_mfd_chip *data = i2c_get_clientdata(client);
@@ -155,10 +181,38 @@ static struct axp_mfd_chip *axp20_update_device(struct
device *dev)
data->acin_power = data->acin_voltage * data->acin_current;
+ if (!axp_hwmon_wq_counter || !data->valid) {
+ data->acin_avg_power = 0;
+ data->acin_avg_voltage = 0;
+ data->acin_avg_current = 0;
+ } else {
+ /* Calculate running moving average for N=64 */
+ data->acin_avg_power -= data->acin_avg_power / 64;
+ data->acin_avg_power += data->acin_voltage *
+ data->acin_current;
+
+ data->acin_avg_voltage -= data->acin_avg_voltage / 64;
+ data->acin_avg_voltage += data->acin_voltage;
+
+ data->acin_avg_current -= data->acin_avg_current / 64;
+ data->acin_avg_current += data->acin_current;
+ }
+
data->last_updated = jiffies;
data->valid = 1;
}
+ if (from_wq) {
+ if (axp_hwmon_wq_counter > 0) {
+ queue_delayed_work(wq, &axp_hwmon_work, HZ / 25);
+ axp_hwmon_wq_counter--;
+ }
+ } else {
+ /* Keep running for at least 1 minute from now */
+ axp_hwmon_wq_counter = 61 * 25;
+ queue_delayed_work(wq, &axp_hwmon_work, HZ / 25);
+ }
+
mutex_unlock(&data->lock);
return data;
}
@@ -217,6 +271,8 @@ static int __devinit axp20_init_chip(struct axp_mfd_chip
*chip)
err = PTR_ERR(chip->hwmon_dev);
goto exit_remove_files;
}
+ wq_dev_arg = chip->dev;
+ wq = create_singlethread_workqueue("axp_hwmon_wq");
} else {
dev_info(chip->dev, "AXP internal temperature monitoring
disabled\n");
/* TODO enable it ?*/
diff --git a/include/linux/mfd/axp-mfd.h b/include/linux/mfd/axp-mfd.h
index 1226d17..f6beb5b 100644
--- a/include/linux/mfd/axp-mfd.h
+++ b/include/linux/mfd/axp-mfd.h
@@ -136,6 +136,9 @@ struct axp_mfd_chip {
s16 acin_voltage; /* range from 0 to 6962 mV */
s16 acin_current; /* range from 0 to 2559 mA */
s32 acin_power;
+ s32 acin_avg_voltage;
+ s32 acin_avg_current;
+ s32 acin_avg_power;
unsigned long last_updated; /* in jiffies */
char valid; /* zero until following fields are valid */
struct device *hwmon_dev;