[NEW DRIVER V1 6/7] DA9058 HWMON driver

From: Anthony Olech
Date: Thu Aug 02 2012 - 04:50:57 EST


This is the HWMON component driver of the Dialog DA9058 PMIC.
This driver is just one component of the whole DA9058 PMIC driver.
It depends on the core DA9058 MFD driver.

Signed-off-by: Tony Olech (at Home) <tony@xxxxxxxxx>
---
drivers/hwmon/Kconfig | 10 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/da9058-hwmon.c | 389 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 400 insertions(+), 0 deletions(-)
create mode 100644 drivers/hwmon/da9058-hwmon.c

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 6f1d167..0986f43 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -294,6 +294,16 @@ config SENSORS_ATXP1
This driver can also be built as a module. If so, the module
will be called atxp1.

+config SENSORS_DA9058
+ tristate "Dialog Semiconductor DA9058 ADC"
+ depends on I2C
+ help
+ If you say yes here you get support for ADC on the Dialog
+ Semiconductor DA9058 PMIC.
+
+ This driver can also be built as a module. If so, the module
+ will be called da9058-hwmon.
+
config SENSORS_DS620
tristate "Dallas Semiconductor DS620"
depends on I2C
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index e1eeac1..be99572 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -43,6 +43,7 @@ obj-$(CONFIG_SENSORS_ASC7621) += asc7621.o
obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o
obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o
obj-$(CONFIG_SENSORS_DME1737) += dme1737.o
+obj-$(CONFIG_SENSORS_DA9058) += da9058-hwmon.o
obj-$(CONFIG_SENSORS_DS620) += ds620.o
obj-$(CONFIG_SENSORS_DS1621) += ds1621.o
obj-$(CONFIG_SENSORS_EMC1403) += emc1403.o
diff --git a/drivers/hwmon/da9058-hwmon.c b/drivers/hwmon/da9058-hwmon.c
new file mode 100644
index 0000000..bc3b89c
--- /dev/null
+++ b/drivers/hwmon/da9058-hwmon.c
@@ -0,0 +1,389 @@
+/*
+ * Copyright (C) 2012 Dialog Semiconductor Ltd.
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/core.h>
+
+#include <linux/mfd/da9058/version.h>
+#include <linux/mfd/da9058/registers.h>
+#include <linux/mfd/da9058/core.h>
+#include <linux/mfd/da9058/hwmon.h>
+
+struct da9058_hwmon {
+ struct da9058 *da9058;
+ struct platform_device *pdev;
+
+ struct device *class_device;
+ struct mutex hwmon_lock;
+ int use_automatic_adc;
+ int temp_adc_resistance;
+ int vf_adc_resistance;
+};
+
+static ssize_t da9058_read_tbat(struct device *dev,
+ struct device_attribute *devattr, char *buf)
+{
+ struct da9058_hwmon *hwmon = dev_get_drvdata(dev);
+ int voltage; /* x000 .. xFFF = 0 .. 2500 mV */
+ int ret;
+
+ ret = da9058_adc_read(hwmon->da9058, DA9058_ADCMAN_MUXSEL_TEMP,
+ hwmon->use_automatic_adc, &voltage);
+ if (ret)
+ return ret;
+
+ return sprintf(buf, "%d\n", voltage * 2500 / 0xFFF);
+}
+
+static ssize_t da9058_read_vbat(struct device *dev,
+ struct device_attribute *devattr, char *buf)
+{
+ struct da9058_hwmon *hwmon = dev_get_drvdata(dev);
+ int voltage; /* x000 .. xFFF = 2500 .. 4500 mV */
+ int ret;
+
+ ret = da9058_adc_read(hwmon->da9058, DA9058_ADCMAN_MUXSEL_VBAT,
+ hwmon->use_automatic_adc, &voltage);
+ if (ret)
+ return ret;
+
+ return sprintf(buf, "%d\n", 2500 + voltage * 2000 / 0xFFF);
+}
+
+static ssize_t da9058_read_misc_channel(struct device *dev,
+ struct device_attribute *devattr,
+ char *buf)
+{
+ struct da9058_hwmon *hwmon = dev_get_drvdata(dev);
+ int channel = to_sensor_dev_attr(devattr)->index;
+ int voltage; /* xFFF .. x800 = 0 .. 2500 mV */
+ int ret;
+
+ ret = da9058_adc_read(hwmon->da9058, channel,
+ hwmon->use_automatic_adc, &voltage);
+ if (ret)
+ return ret;
+
+ return sprintf(buf, "%d\n", (0xFFF - voltage) * 2500 / 0x7FF);
+}
+
+/*
+ * The algorithm for converting the value is
+ * Degrees celsius = 1.708 * (TJUNC_RES - T_OFFSET) - 108.8
+ * T_OFFSET is a trim value used to improve accuracy of the result
+ */
+static ssize_t da9058_read_tjunc(struct device *dev,
+ struct device_attribute *devattr, char *buf)
+{
+ struct da9058_hwmon *hwmon = dev_get_drvdata(dev);
+ int tjunc;
+ u8 toffset;
+ int ret;
+
+ ret = da9058_reg_read(hwmon->da9058, DA9058_TOFFSET_REG, &toffset);
+ if (ret < 0)
+ return ret;
+
+ ret = da9058_adc_read(hwmon->da9058, DA9058_ADCMAN_MUXSEL_TJUNC,
+ hwmon->use_automatic_adc, &tjunc);
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%d\n", 1708 * (tjunc - (int)toffset) - 108800);
+}
+
+static ssize_t da9058_read_vfpin(struct device *dev,
+ struct device_attribute *devattr, char *buf)
+{
+ struct da9058_hwmon *hwmon = dev_get_drvdata(dev);
+ int voltage; /* x000 .. xFFF = 0 .. 4095 mV */
+ int ret;
+
+ ret = da9058_adc_read(hwmon->da9058, DA9058_ADCMAN_MUXSEL_VF,
+ hwmon->use_automatic_adc, &voltage);
+ if (ret)
+ return ret;
+
+ return sprintf(buf, "%d\n", voltage);
+}
+
+static ssize_t da9058_vfpin_mode(struct device *dev,
+ struct device_attribute *devattr, char *buf)
+{
+ struct da9058_hwmon *hwmon = dev_get_drvdata(dev);
+ int ret;
+ u8 mode;
+
+ ret = da9058_reg_read(hwmon->da9058, DA9058_ADCCONT_REG, &mode);
+ if (ret)
+ return ret;
+
+ if (mode & DA9058_ADCCONT_VFISRCEN) {
+ if (hwmon->vf_adc_resistance)
+ return sprintf(buf, "resistance\n");
+ else
+ return sprintf(buf,
+ "resistance - but should be voltage\n");
+ } else {
+ if (hwmon->vf_adc_resistance)
+ return sprintf(buf,
+ "voltage - but should be resistance\n");
+ else
+ return sprintf(buf, "voltage\n");
+ }
+}
+
+static ssize_t da9058_get_adc_mode(struct device *dev,
+ struct device_attribute *devattr,
+ char *buf)
+{
+ struct da9058_hwmon *hwmon = dev_get_drvdata(dev);
+ u8 mode;
+ int ret;
+
+ ret = da9058_reg_read(hwmon->da9058, DA9058_ADCCONT_REG, &mode);
+ if (ret)
+ return ret;
+
+ if (mode & DA9058_ADCCONT_AUTOADCEN) {
+ if (hwmon->use_automatic_adc)
+ return sprintf(buf, "automatic\n");
+ else
+ return sprintf(buf,
+ "automatic - but should be manual\n");
+ } else {
+ if (hwmon->use_automatic_adc)
+ return sprintf(buf,
+ "manual - but should be automatic\n");
+ else
+ return sprintf(buf, "manual\n");
+ }
+}
+
+static ssize_t da9058_set_adc_mode(struct device *dev,
+ struct device_attribute *devattr,
+ const char *buf, size_t count)
+{
+ struct da9058_hwmon *hwmon = dev_get_drvdata(dev);
+
+ if (*buf == 'A' || *buf == 'a') {
+ if (hwmon->use_automatic_adc) {
+ dev_info(&hwmon->pdev->dev,
+ "already in AUTOMATIC adc_mode\n");
+ return -EINVAL;
+ } else {
+ int ret;
+ int mode = DA9058_ADCCONT_AUTOADCEN |
+ DA9058_ADCCONT_TEMPISRCEN |
+ DA9058_ADCCONT_AUTOVBATEN |
+ DA9058_ADCCONT_AUTOVFEN |
+ DA9058_ADCCONT_AUTOAINEN;
+
+ if (hwmon->vf_adc_resistance)
+ mode |= DA9058_ADCCONT_VFISRCEN;
+
+ hwmon->use_automatic_adc = 1;
+ ret = da9058_reg_write(hwmon->da9058,
+ DA9058_ADCCONT_REG, mode);
+ if (ret)
+ return ret;
+ return count;
+ }
+
+ } else if (*buf == 'M' || *buf == 'm') {
+ if (hwmon->use_automatic_adc) {
+ int ret;
+ int mode = 0;
+
+ if (hwmon->temp_adc_resistance)
+ mode |= DA9058_ADCCONT_TEMPISRCEN;
+ if (hwmon->vf_adc_resistance)
+ mode |= DA9058_ADCCONT_VFISRCEN;
+
+ hwmon->use_automatic_adc = 0;
+ ret = da9058_reg_write(hwmon->da9058,
+ DA9058_ADCCONT_REG, mode);
+ if (ret)
+ return ret;
+ return count;
+ } else {
+ dev_info(&hwmon->pdev->dev,
+ "already in MANUAL adc_mode\n");
+ return -EINVAL;
+ }
+ } else {
+ dev_err(&hwmon->pdev->dev,
+ "adc_mode should be AUTOMATIC or MANUAL not '%s'",
+ buf);
+ return -EINVAL;
+ }
+}
+
+static ssize_t da9058_hwmon_show_name(struct device *dev,
+ struct device_attribute *devattr,
+ char *buf)
+{
+ return sprintf(buf, "da9058-hwmon\n");
+}
+
+static SENSOR_DEVICE_ATTR(vbat_mV, S_IRUGO, da9058_read_vbat, NULL, 0);
+static SENSOR_DEVICE_ATTR(adc_mV, S_IRUGO, da9058_read_misc_channel, NULL,
+ DA9058_ADCMAN_MUXSEL_ADCIN);
+static SENSOR_DEVICE_ATTR(vfpin_mV, S_IRUGO, da9058_read_vfpin, NULL, 0);
+static SENSOR_DEVICE_ATTR(vfpin_mode, S_IRUGO, da9058_vfpin_mode, NULL, 0);
+static SENSOR_DEVICE_ATTR(tbat_mV, S_IRUGO, da9058_read_tbat, NULL, 0);
+static SENSOR_DEVICE_ATTR(tjunc_in, S_IRUGO, da9058_read_tjunc, NULL, 0);
+static SENSOR_DEVICE_ATTR(adc_mode, S_IWUSR | S_IRUGO, da9058_get_adc_mode,
+ da9058_set_adc_mode, 0);
+
+static DEVICE_ATTR(name, S_IRUGO, da9058_hwmon_show_name, NULL);
+
+static struct attribute *da9058_attr[] = {
+ &dev_attr_name.attr,
+ &sensor_dev_attr_adc_mode.dev_attr.attr,
+ &sensor_dev_attr_vbat_mV.dev_attr.attr,
+ &sensor_dev_attr_adc_mV.dev_attr.attr,
+ &sensor_dev_attr_vfpin_mV.dev_attr.attr,
+ &sensor_dev_attr_vfpin_mode.dev_attr.attr,
+ &sensor_dev_attr_tbat_mV.dev_attr.attr,
+ &sensor_dev_attr_tjunc_in.dev_attr.attr,
+ NULL
+};
+
+/*
+ * the attributes will appear in /sys/class/hwmon/hwmon0/device
+ */
+
+static const struct attribute_group da9058_attr_group = {.attrs = da9058_attr};
+
+static int __devinit da9058_hwmon_probe(struct platform_device *pdev)
+{
+ struct da9058 *da9058 = dev_get_drvdata(pdev->dev.parent);
+ const struct mfd_cell *cell = mfd_get_cell(pdev);
+ struct da9058_hwmon_pdata *hwmon_pdata;
+ struct da9058_hwmon *hwmon;
+ int ret;
+
+ if (cell == NULL) {
+ ret = -ENODEV;
+ goto exit;
+ }
+
+ hwmon_pdata = cell->platform_data;
+
+ if (hwmon_pdata == NULL) {
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ if (hwmon_pdata->use_automatic_adc &
+ !hwmon_pdata->temp_adc_resistance) {
+ ret = -EINVAL; /* impossible setting */
+ goto exit;
+ }
+
+ dev_info(&pdev->dev, "HWMON uses %s ADC conversions\n",
+ hwmon_pdata->use_automatic_adc ? "automatic" : "manual");
+
+ hwmon = devm_kzalloc(&pdev->dev, sizeof(struct da9058_hwmon),
+ GFP_KERNEL);
+ if (!hwmon) {
+ ret = -ENOMEM;
+ goto exit;
+ }
+
+ platform_set_drvdata(pdev, hwmon);
+
+ hwmon->da9058 = da9058;
+ hwmon->pdev = pdev;
+ hwmon->use_automatic_adc = hwmon_pdata->use_automatic_adc;
+ hwmon->temp_adc_resistance = hwmon_pdata->temp_adc_resistance;
+ hwmon->vf_adc_resistance = hwmon_pdata->vf_adc_resistance;
+
+ if (hwmon->use_automatic_adc) {
+ int mode = DA9058_ADCCONT_AUTOADCEN |
+ DA9058_ADCCONT_TEMPISRCEN |
+ DA9058_ADCCONT_AUTOVBATEN |
+ DA9058_ADCCONT_AUTOVFEN |
+ DA9058_ADCCONT_AUTOAINEN;
+
+ if (hwmon->vf_adc_resistance)
+ mode |= DA9058_ADCCONT_VFISRCEN;
+
+ ret = da9058_reg_write(da9058, DA9058_ADCCONT_REG, mode);
+ if (ret)
+ goto err1;
+ } else {
+ int mode = 0;
+
+ if (hwmon->temp_adc_resistance)
+ mode |= DA9058_ADCCONT_TEMPISRCEN;
+ if (hwmon->vf_adc_resistance)
+ mode |= DA9058_ADCCONT_VFISRCEN;
+
+ ret = da9058_reg_write(da9058, DA9058_ADCCONT_REG, mode);
+ if (ret)
+ goto err1;
+ }
+
+ mutex_init(&hwmon->hwmon_lock);
+
+ ret = sysfs_create_group(&pdev->dev.kobj, &da9058_attr_group);
+ if (ret)
+ goto err1;
+
+ hwmon->class_device = hwmon_device_register(&pdev->dev);
+ if (IS_ERR(hwmon->class_device)) {
+ ret = PTR_ERR(hwmon->class_device);
+ goto err2;
+ }
+
+ goto exit;
+
+err2:
+ sysfs_remove_group(&pdev->dev.kobj, &da9058_attr_group);
+err1:
+exit:
+ return ret;
+}
+
+static int __devexit da9058_hwmon_remove(struct platform_device *pdev)
+{
+ struct da9058_hwmon *hwmon = platform_get_drvdata(pdev);
+
+ hwmon_device_unregister(hwmon->class_device);
+ sysfs_remove_group(&pdev->dev.kobj, &da9058_attr_group);
+
+ return 0;
+}
+
+static struct platform_driver da9058_hwmon_driver = {
+ .probe = da9058_hwmon_probe,
+ .remove = __devexit_p(da9058_hwmon_remove),
+ .driver = {
+ .name = "da9058-hwmon",
+ .owner = THIS_MODULE,
+ },
+};
+
+module_platform_driver(da9058_hwmon_driver);
+
+MODULE_DESCRIPTION("Dialog DA9058 PMIC HardWare Monitor Driver");
+MODULE_AUTHOR("Anthony Olech <Anthony.Olech@xxxxxxxxxxx>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:da9058-hwmon");
--
end-of-patch for NEW DRIVER V1

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/