AXP202 and AXP209 can report voltages and current readings for its various power inputs, the LiPo battery, and also the chip's internal temperature. This patch add basic support for these sensors.
Signed-off-by: Chen-Yu Tsai <[email protected]> Signed-off-by: Corentin Labbe <[email protected]> --- drivers/mfd/Kconfig | 11 ++ drivers/mfd/Makefile | 1 + drivers/mfd/axp20x-hwmon.c | 265 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 277 insertions(+) create mode 100644 drivers/mfd/axp20x-hwmon.c diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 1ed0584..e0a3944 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -150,6 +150,17 @@ config MFD_AXP20X_RSB components like regulators or the PEK (Power Enable Key) under the corresponding menus. +config MFD_AXP20X_HWMON + tristate "X-POWERS AXP20X PMIC hwmon" + depends on MFD_AXP20X + depends on HWMON + 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 MFD_CROS_EC tristate "ChromeOS Embedded Controller" select MFD_CORE diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 7bb5a501..91b43bd 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -120,6 +120,7 @@ obj-$(CONFIG_MFD_AC100) += ac100.o obj-$(CONFIG_MFD_AXP20X) += axp20x.o obj-$(CONFIG_MFD_AXP20X_I2C) += axp20x-i2c.o obj-$(CONFIG_MFD_AXP20X_RSB) += axp20x-rsb.o +obj-$(CONFIG_MFD_AXP20X_HWMON) += axp20x-hwmon.o obj-$(CONFIG_MFD_LP3943) += lp3943.o obj-$(CONFIG_MFD_LP8788) += lp8788.o lp8788-irq.o diff --git a/drivers/mfd/axp20x-hwmon.c b/drivers/mfd/axp20x-hwmon.c new file mode 100644 index 0000000..02b4028 --- /dev/null +++ b/drivers/mfd/axp20x-hwmon.c @@ -0,0 +1,265 @@ +/* + * axp20x ADC hwmon driver. + * + * Copyright (C) 2013 Chen-Yu Tsai <[email protected]> + * + * 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/err.h> +#include <linux/init.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#include <linux/mfd/axp20x.h> + +/* valid bits for ADC enable registers */ +#define AXP20X_ADC_EN1_MASK 0xff +#define AXP20X_ADC_EN2_MASK 0x8c + +/* default values from the datasheet */ +#define AXP20X_ADC_EN1_DEFAULT 0x83 +#define AXP20X_ADC_EN2_DEFAULT 0x80 + +/* enable bits for basic ADCs */ +#define AXP20X_ADC_EN1_BASIC 0xfe +#define AXP20X_ADC_EN2_BASIC 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] = "APS", +}; + +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 across 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]; + + 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, 0444, show_voltage,\ + NULL, name); \ + static SENSOR_DEVICE_ATTR(in##id##_label, 0444, show_label,\ + NULL, name) + +#define AXP20X_NAMED_CURRENT(id, name) \ + static SENSOR_DEVICE_ATTR(curr##id##_input, 0444, show_voltage,\ + NULL, name); \ + static SENSOR_DEVICE_ATTR(curr##id##_label, 0444, show_label,\ + NULL, name) + +#define AXP20X_NAMED_POWER(id, name) \ + static SENSOR_DEVICE_ATTR(power##id##_input, 0444, show_voltage,\ + NULL, name);\ + static SENSOR_DEVICE_ATTR(power##id##_label, 0444, show_label,\ + NULL, name) + +#define AXP20X_NAMED_TEMP(id, name) \ + static SENSOR_DEVICE_ATTR(temp##id##_input, 0444, show_temp,\ + NULL, name); \ + static SENSOR_DEVICE_ATTR(temp##id##_label, 0444, 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 ret; + + if (!axp20x) { + dev_err(&pdev->dev, "Cannot find axp20x device\n"); + return -ENODEV; + } + + platform_set_drvdata(pdev, axp20x); + + ret = regmap_update_bits(axp20x->regmap, AXP20X_ADC_EN1, + AXP20X_ADC_EN1_MASK, AXP20X_ADC_EN1_BASIC); + if (!ret) + ret = regmap_update_bits(axp20x->regmap, AXP20X_ADC_EN2, + AXP20X_ADC_EN2_MASK, + AXP20X_ADC_EN2_BASIC); + + if (ret) { + dev_err(&pdev->dev, "Failed to enable ADCs\n"); + goto err; + } + + hwmon_dev = devm_hwmon_device_register_with_groups( + &pdev->dev, + "axp20x_hwmon", + axp20x, + axp20x_groups + ); + if (IS_ERR(hwmon_dev)) { + ret = PTR_ERR(hwmon_dev); + dev_err(&pdev->dev, "Could not register hwmon %d\n", ret); + goto err_adc_en; + } + + return 0; + +err_adc_en: + regmap_update_bits(axp20x->regmap, AXP20X_ADC_EN1, AXP20X_ADC_EN1_MASK, + AXP20X_ADC_EN1_DEFAULT); + regmap_update_bits(axp20x->regmap, AXP20X_ADC_EN2, AXP20X_ADC_EN2_MASK, + AXP20X_ADC_EN2_DEFAULT); +err: + return ret; +} + +static int axp20x_hwmon_remove(struct platform_device *pdev) +{ + struct axp20x_dev *axp20x = platform_get_drvdata(pdev); + + regmap_update_bits(axp20x->regmap, AXP20X_ADC_EN1, AXP20X_ADC_EN1_MASK, + AXP20X_ADC_EN1_DEFAULT); + regmap_update_bits(axp20x->regmap, AXP20X_ADC_EN2, AXP20X_ADC_EN2_MASK, + AXP20X_ADC_EN2_DEFAULT); + + 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 <[email protected]>"); +MODULE_DESCRIPTION("Hardware monitoring driver for AXP20X PMIC"); -- 2.10.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.
