The new axp20x hwmon sysfs entry "axp20_hwmon_enable_polling_for_average"
allows to enable 25Hz periodic sampling of ACIN voltage/current and
calculate moving average. It gets activated if set to "1" and disabled
if set to "0".

This periodic sampling and calculating moving average is disabled by
default. Because waking up the CPU many times per second is not nice
for many reasons (battery life, performance, reliability, etc.)
Still it has lower overhead than doing the same in the userland and
may be useful for some power consumption tests.

Comparison of the current draw (mA) for different workloads, measured
by the axp20x hwmon (on the left side) and with a multimeter (on the
right side) for different sunxi hardware and without anything extra
on 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.

Signed-off-by: Siarhei Siamashka <[email protected]>
---
 drivers/power/axp_power/axp-mfd.c   |  4 ++
 drivers/power/axp_power/axp20-mfd.h | 73 +++++++++++++++++++++++++++++++++++++
 include/linux/mfd/axp-mfd.h         |  3 ++
 3 files changed, 80 insertions(+)

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 8d679ac..75c522a 100644
--- a/drivers/power/axp_power/axp20-mfd.h
+++ b/drivers/power/axp_power/axp20-mfd.h
@@ -52,6 +52,8 @@ show_acin_voltage(struct device *dev, struct device_attribute 
*devattr,
        struct axp_mfd_chip *data = axp20_update_device(dev);
        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);
 }
 
@@ -63,6 +65,8 @@ show_acin_current(struct device *dev, struct device_attribute 
*devattr,
        struct axp_mfd_chip *data = axp20_update_device(dev);
        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);
 }
 
@@ -73,6 +77,8 @@ show_acin_power(struct device *dev, struct device_attribute 
*devattr, char *buf)
        struct axp_mfd_chip *data = axp20_update_device(dev);
        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 +88,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 +103,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,6 +137,8 @@ static int axp_read_adc(struct device *dev, struct 
i2c_client *client, int reg)
        return (high << 4) + (low & 0x0F);
 }
 
+static int axp_hwmon_wq_enabled;
+
 /*
  *  * function that update the status of the chips (temperature)
  *   */
@@ -156,6 +170,23 @@ static struct axp_mfd_chip *axp20_update_device(struct 
device *dev)
 
                data->acin_power = data->acin_voltage * data->acin_current;
 
+               /* 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;
+
+               /* But if we have no active polling, they are meaningless */
+               if (!axp_hwmon_wq_enabled || !data->valid) {
+                       data->acin_avg_power = 0;
+                       data->acin_avg_voltage = 0;
+                       data->acin_avg_current = 0;
+               }
+
                data->last_updated = jiffies;
                data->valid = 1;
        }
@@ -164,6 +195,18 @@ static struct axp_mfd_chip *axp20_update_device(struct 
device *dev)
        return data;
 }
 
+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);
+       queue_delayed_work(wq, &axp_hwmon_work, HZ / 25);
+}
+
 #endif
 
 
@@ -218,6 +261,10 @@ static int __devinit axp20_init_chip(struct axp_mfd_chip 
*chip)
                        err = PTR_ERR(chip->hwmon_dev);
                        goto exit_remove_files;
                }
+               wq = create_singlethread_workqueue("axp_hwmon_wq");
+               wq_dev_arg = chip->dev;
+               if (axp_hwmon_wq_enabled)
+                       queue_delayed_work(wq, &axp_hwmon_work, HZ / 25);
        } else {
                dev_info(chip->dev, "AXP internal temperature monitoring 
disabled\n");
                /* TODO enable it ?*/
@@ -558,6 +605,29 @@ static ssize_t axp20_regs_store(struct device *dev,
        return count;
 }
 
+#ifdef CONFIG_AXP_HWMON
+static ssize_t axp20_hwmon_enable_polling_for_average_show(struct device *dev,
+                               struct device_attribute *attr, char *buf)
+{
+       return sprintf(buf, "%d\n", axp_hwmon_wq_enabled);
+}
+
+static ssize_t axp20_hwmon_enable_polling_for_average_store(struct device *dev,
+                               struct device_attribute *attr, const char *buf,
+                               size_t count)
+{
+       axp_hwmon_wq_enabled = simple_strtoul(buf, NULL, 10);
+
+       cancel_delayed_work(&axp_hwmon_work);
+       flush_workqueue(wq);
+
+       if (axp_hwmon_wq_enabled)
+               queue_delayed_work(wq, &axp_hwmon_work, HZ / 25);
+
+       return count;
+}
+#endif
+
 static struct device_attribute axp20_mfd_attrs[] = {
        AXP_MFD_ATTR(axp20_offvol),
        AXP_MFD_ATTR(axp20_noedelay),
@@ -569,4 +639,7 @@ static struct device_attribute axp20_mfd_attrs[] = {
        AXP_MFD_ATTR(axp20_ovtemclsen),
        AXP_MFD_ATTR(axp20_reg),
        AXP_MFD_ATTR(axp20_regs),
+#ifdef CONFIG_AXP_HWMON
+       AXP_MFD_ATTR(axp20_hwmon_enable_polling_for_average),
+#endif
 };
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;
-- 
1.8.3.2

-- 
You received this message because you are subscribed to the Google Groups 
"linux-sunxi" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.

Reply via email to