AXP202 and AXP209 can report voltages and current readings for its
various power inputs, as well as the LiPo battery. There is also an
internal temperature sensor. This patch adds basic support for them.

Signed-off-by: Chen-Yu Tsai <w...@csie.org>
---

Hi everyone,

This is a basic hwmon driver for the AXP209 PMIC. All the internal sensors
are supported. DT support and ADC on GPIO pins, among other things, are
missing. The driver was hastily put together in a couple of hours by copy
and pasting, so feel free to criticize. I just thought I'd get it out there
first. :)

The ACIN/VBUS current sensors are not accurate on the Cubie* boards. These
have bypasses for things like USB VBUS and SATA power.


Cheers
ChenYu


 drivers/hwmon/Kconfig        |  10 ++
 drivers/hwmon/Makefile       |   1 +
 drivers/hwmon/axp20x-hwmon.c | 242 +++++++++++++++++++++++++++++++++++++++++++
 drivers/mfd/axp20x.c         |   3 +
 4 files changed, 256 insertions(+)
 create mode 100644 drivers/hwmon/axp20x-hwmon.c

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 5ce43d8..dcdf918 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -337,6 +337,16 @@ config SENSORS_ATXP1
          This driver can also be built as a module.  If so, the module
          will be called atxp1.
 
+config SENSORS_AXP20X
+       tristate "X-POWERS AXP20X PMIC"
+       depends on MFD_AXP20X
+       help
+         If you say yes here you get support for the hardware
+         monitoring features of the AXP20X series of PMICs.
+
+         This driver can also be built as a module.  If so, the module
+         will be called axp20x-hwmon.
+
 config SENSORS_DS620
        tristate "Dallas Semiconductor DS620"
        depends on I2C
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index ec7cde0..410c622 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -45,6 +45,7 @@ obj-$(CONFIG_SENSORS_ADT7475) += adt7475.o
 obj-$(CONFIG_SENSORS_APPLESMC) += applesmc.o
 obj-$(CONFIG_SENSORS_ASC7621)  += asc7621.o
 obj-$(CONFIG_SENSORS_ATXP1)    += atxp1.o
+obj-$(CONFIG_SENSORS_AXP20X)   += axp20x-hwmon.o
 obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o
 obj-$(CONFIG_SENSORS_DA9052_ADC)+= da9052-hwmon.o
 obj-$(CONFIG_SENSORS_DA9055)+= da9055-hwmon.o
diff --git a/drivers/hwmon/axp20x-hwmon.c b/drivers/hwmon/axp20x-hwmon.c
new file mode 100644
index 0000000..9dd78ea
--- /dev/null
+++ b/drivers/hwmon/axp20x-hwmon.c
@@ -0,0 +1,242 @@
+/*
+ * axp20x regulators driver.
+ *
+ * Copyright (C) 2013 Chen-Yu Tsai <w...@csie.org>
+ *
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License. See the file "COPYING" in the main directory of this
+ * archive for more details.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/regmap.h>
+#include <linux/i2c.h>
+
+#include <linux/mfd/axp20x.h>
+
+#define AXP20X_ADC_EN1_MASK            0xff
+#define AXP20X_ADC_EN2_MASK            0x80
+
+/* Use MSB register offset as index */
+static const char * const input_names[] = {
+       [AXP20X_ACIN_V_ADC_H]   = "ACIN",
+       [AXP20X_ACIN_I_ADC_H]   = "ACIN",
+       [AXP20X_VBUS_V_ADC_H]   = "VBUS",
+       [AXP20X_VBUS_I_ADC_H]   = "VBUS",
+       [AXP20X_TEMP_ADC_H]     = "CHIP",
+       [AXP20X_TS_IN_H]        = "TS",
+       [AXP20X_GPIO0_V_ADC_H]  = "GPIO0",
+       [AXP20X_GPIO1_V_ADC_H]  = "GPIO1",
+       [AXP20X_PWR_BATT_H]     = "BATT",
+       [AXP20X_BATT_V_H]       = "BATT",
+       [AXP20X_BATT_CHRG_I_H]  = "BATT_CHRG",
+       [AXP20X_BATT_DISCHRG_I_H]       = "BATT_DISCHRG",
+       [AXP20X_IPSOUT_V_HIGH_H]        = "IPS_OUT",
+};
+
+static const int input_step[] = {
+       [AXP20X_ACIN_V_ADC_H] = 1700,
+       [AXP20X_ACIN_I_ADC_H] = 625,
+       [AXP20X_VBUS_V_ADC_H] = 1700,
+       [AXP20X_VBUS_I_ADC_H] = 375,
+       [AXP20X_TEMP_ADC_H] = 100,
+       [AXP20X_TS_IN_H] = 800,
+       [AXP20X_GPIO0_V_ADC_H] = 500,
+       [AXP20X_GPIO1_V_ADC_H] = 500,
+       [AXP20X_PWR_BATT_H] = 1100,
+       [AXP20X_BATT_V_H] = 1100,
+       [AXP20X_BATT_CHRG_I_H] = 500,
+       [AXP20X_BATT_DISCHRG_I_H] = 500,
+       [AXP20X_IPSOUT_V_HIGH_H] = 1400,
+};
+
+static int axp20x_adc_read(struct axp20x_dev *axp20x, int channel)
+{
+       unsigned char val[3];
+       int ret;
+
+       if (channel < AXP20X_ACIN_V_ADC_H || channel > AXP20X_IPSOUT_V_HIGH_H)
+               return -EINVAL;
+
+       /* ADC values are split among at most 3 registers */
+       ret = regmap_bulk_read(axp20x->regmap, channel, val, 3);
+       if (ret) {
+               dev_dbg(axp20x->dev, "Read ADC 0x%02x failed: %d\n", channel,
+                               ret);
+               return ret;
+       }
+
+       if (channel == AXP20X_PWR_BATT_H)
+               ret = val[0] << 16 | val[1] << 8 | val[2];
+       else
+               ret = val[0] << 4 | val[1];
+
+       dev_dbg(axp20x->dev, "ADC 0x%02x: 0x%02x 0x%02x 0x%02x => %d\n",
+                       channel, val[0], val[1], val[2], ret);
+
+       return ret;
+}
+
+static ssize_t show_voltage(struct device *dev,
+                           struct device_attribute *attr, char *buf)
+{
+       struct axp20x_dev *axp20x = dev_get_drvdata(dev);
+       int channel = to_sensor_dev_attr(attr)->index;
+       int val;
+
+       val = axp20x_adc_read(axp20x, channel) * input_step[channel];
+       val = DIV_ROUND_CLOSEST(val, 1000);
+
+       return sprintf(buf, "%d\n", val);
+}
+
+static ssize_t show_temp(struct device *dev,
+                           struct device_attribute *attr, char *buf)
+{
+       struct axp20x_dev *axp20x = dev_get_drvdata(dev);
+       int channel = to_sensor_dev_attr(attr)->index;
+       int val;
+
+       val = axp20x_adc_read(axp20x, channel) * input_step[channel] - 144700;
+
+       return sprintf(buf, "%d\n", val);
+}
+
+static ssize_t show_label(struct device *dev,
+                         struct device_attribute *attr, char *buf)
+{
+       int channel = to_sensor_dev_attr(attr)->index;
+
+       return sprintf(buf, "%s\n", input_names[channel]);
+}
+
+#define AXP20X_NAMED_VOLTAGE(id, name) \
+       static SENSOR_DEVICE_ATTR(in##id##_input, S_IRUGO, show_voltage,\
+                                 NULL, name);          \
+       static SENSOR_DEVICE_ATTR(in##id##_label, S_IRUGO, show_label,\
+                                 NULL, name)
+
+#define AXP20X_NAMED_CURRENT(id, name) \
+       static SENSOR_DEVICE_ATTR(curr##id##_input, S_IRUGO, show_voltage,\
+                                 NULL, name);          \
+       static SENSOR_DEVICE_ATTR(curr##id##_label, S_IRUGO, show_label,\
+                                 NULL, name)
+
+#define AXP20X_NAMED_POWER(id, name) \
+       static SENSOR_DEVICE_ATTR(power##id##_input, S_IRUGO, show_voltage,\
+                                 NULL, name);          \
+       static SENSOR_DEVICE_ATTR(power##id##_label, S_IRUGO, show_label,\
+                                 NULL, name)
+
+#define AXP20X_NAMED_TEMP(id, name) \
+       static SENSOR_DEVICE_ATTR(temp##id##_input, S_IRUGO, show_temp,\
+                                 NULL, name);          \
+       static SENSOR_DEVICE_ATTR(temp##id##_label, S_IRUGO, show_label,\
+                                 NULL, name)
+
+AXP20X_NAMED_VOLTAGE(0, AXP20X_ACIN_V_ADC_H);
+AXP20X_NAMED_VOLTAGE(1, AXP20X_VBUS_V_ADC_H);
+AXP20X_NAMED_VOLTAGE(2, AXP20X_BATT_V_H);
+AXP20X_NAMED_VOLTAGE(3, AXP20X_IPSOUT_V_HIGH_H);
+AXP20X_NAMED_CURRENT(1, AXP20X_ACIN_I_ADC_H);
+AXP20X_NAMED_CURRENT(2, AXP20X_VBUS_I_ADC_H);
+AXP20X_NAMED_CURRENT(3, AXP20X_BATT_CHRG_I_H);
+AXP20X_NAMED_CURRENT(4, AXP20X_BATT_DISCHRG_I_H);
+AXP20X_NAMED_POWER(1, AXP20X_PWR_BATT_H);
+AXP20X_NAMED_TEMP(1, AXP20X_TEMP_ADC_H);
+
+static struct attribute *axp20x_attrs[] = {
+       &sensor_dev_attr_in0_input.dev_attr.attr,
+       &sensor_dev_attr_in0_label.dev_attr.attr,
+       &sensor_dev_attr_in1_input.dev_attr.attr,
+       &sensor_dev_attr_in1_label.dev_attr.attr,
+       &sensor_dev_attr_in2_input.dev_attr.attr,
+       &sensor_dev_attr_in2_label.dev_attr.attr,
+       &sensor_dev_attr_in3_input.dev_attr.attr,
+       &sensor_dev_attr_in3_label.dev_attr.attr,
+
+       &sensor_dev_attr_curr1_input.dev_attr.attr,
+       &sensor_dev_attr_curr1_label.dev_attr.attr,
+       &sensor_dev_attr_curr2_input.dev_attr.attr,
+       &sensor_dev_attr_curr2_label.dev_attr.attr,
+       &sensor_dev_attr_curr3_input.dev_attr.attr,
+       &sensor_dev_attr_curr3_label.dev_attr.attr,
+       &sensor_dev_attr_curr4_input.dev_attr.attr,
+       &sensor_dev_attr_curr4_label.dev_attr.attr,
+
+       &sensor_dev_attr_power1_input.dev_attr.attr,
+       &sensor_dev_attr_power1_label.dev_attr.attr,
+
+       &sensor_dev_attr_temp1_input.dev_attr.attr,
+       &sensor_dev_attr_temp1_label.dev_attr.attr,
+
+       NULL,
+};
+ATTRIBUTE_GROUPS(axp20x);
+
+static int axp20x_hwmon_probe(struct platform_device *pdev)
+{
+       struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
+       struct device *hwmon_dev;
+       int val[2] = {AXP20X_ADC_EN1_MASK, AXP20X_ADC_EN2_MASK};
+       int ret;
+
+       ret = regmap_bulk_write(axp20x->regmap, AXP20X_ADC_EN1, val, 2);
+       if (ret) {
+               dev_err(&pdev->dev, "Failed to enable ADCs\n");
+               goto err;
+       }
+
+       hwmon_dev = devm_hwmon_device_register_with_groups(
+                       &pdev->dev,
+                       axp20x->i2c_client->name,
+                       axp20x,
+                       axp20x_groups
+                       );
+       if (IS_ERR(hwmon_dev))
+               goto err_adc_en;
+
+       return 0;
+
+err_adc_en:
+       val[0] = val[1] = 0;
+       regmap_bulk_write(axp20x->regmap, AXP20X_ADC_EN1, val, 2);
+err:
+       return ret;
+}
+
+static int axp20x_hwmon_remove(struct platform_device *pdev)
+{
+       struct axp20x_dev *axp20x = platform_get_drvdata(pdev);
+       int val[2] = {0};
+
+       regmap_bulk_write(axp20x->regmap, AXP20X_ADC_EN1, val, 2);
+
+       return 0;
+}
+
+static struct platform_driver axp20x_hwmon_driver = {
+       .probe = axp20x_hwmon_probe,
+       .remove = axp20x_hwmon_remove,
+       .driver = {
+               .name = "axp20x-hwmon",
+               .owner = THIS_MODULE,
+       },
+};
+
+module_platform_driver(axp20x_hwmon_driver);
+
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Chen-Yu Tsai <w...@csie.org>");
+MODULE_DESCRIPTION("Hardware Monitoring Driver for AXP20X PMIC");
diff --git a/drivers/mfd/axp20x.c b/drivers/mfd/axp20x.c
index 92e5b0f..92dc327 100644
--- a/drivers/mfd/axp20x.c
+++ b/drivers/mfd/axp20x.c
@@ -31,6 +31,7 @@ static const struct regmap_range axp20x_writeable_ranges[] = {
 
 static const struct regmap_range axp20x_volatile_ranges[] = {
        regmap_reg_range(AXP20X_IRQ1_EN, AXP20X_IRQ5_STATE),
+       regmap_reg_range(AXP20X_ACIN_V_ADC_H, AXP20X_IPSOUT_V_HIGH_L),
 };
 
 static const struct regmap_access_table axp20x_writeable_table = {
@@ -130,6 +131,8 @@ static struct mfd_cell axp20x_cells[] = {
                .resources      = axp20x_pek_resources,
        }, {
                .name           = "axp20x-regulator",
+       }, {
+               .name           = "axp20x-hwmon",
        },
 };
 
-- 
1.9.0

-- 
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 linux-sunxi+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Reply via email to