linux/drivers/hwmon/da9055-hwmon.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * HWMON Driver for Dialog DA9055
   4 *
   5 * Copyright(c) 2012 Dialog Semiconductor Ltd.
   6 *
   7 * Author: David Dajun Chen <dchen@diasemi.com>
   8 */
   9
  10#include <linux/delay.h>
  11#include <linux/err.h>
  12#include <linux/hwmon.h>
  13#include <linux/hwmon-sysfs.h>
  14#include <linux/init.h>
  15#include <linux/kernel.h>
  16#include <linux/module.h>
  17#include <linux/platform_device.h>
  18#include <linux/completion.h>
  19
  20#include <linux/mfd/da9055/core.h>
  21#include <linux/mfd/da9055/reg.h>
  22
  23#define DA9055_ADCIN_DIV        102
  24#define DA9055_VSYS_DIV 85
  25
  26#define DA9055_ADC_VSYS 0
  27#define DA9055_ADC_ADCIN1       1
  28#define DA9055_ADC_ADCIN2       2
  29#define DA9055_ADC_ADCIN3       3
  30#define DA9055_ADC_TJUNC        4
  31
  32struct da9055_hwmon {
  33        struct da9055   *da9055;
  34        struct mutex    hwmon_lock;
  35        struct mutex    irq_lock;
  36        struct completion done;
  37};
  38
  39static const char * const input_names[] = {
  40        [DA9055_ADC_VSYS]       = "VSYS",
  41        [DA9055_ADC_ADCIN1]     = "ADC IN1",
  42        [DA9055_ADC_ADCIN2]     = "ADC IN2",
  43        [DA9055_ADC_ADCIN3]     = "ADC IN3",
  44        [DA9055_ADC_TJUNC]      = "CHIP TEMP",
  45};
  46
  47static const u8 chan_mux[DA9055_ADC_TJUNC + 1] = {
  48        [DA9055_ADC_VSYS]       = DA9055_ADC_MUX_VSYS,
  49        [DA9055_ADC_ADCIN1]     = DA9055_ADC_MUX_ADCIN1,
  50        [DA9055_ADC_ADCIN2]     = DA9055_ADC_MUX_ADCIN2,
  51        [DA9055_ADC_ADCIN3]     = DA9055_ADC_MUX_ADCIN3,
  52        [DA9055_ADC_TJUNC]      = DA9055_ADC_MUX_T_SENSE,
  53};
  54
  55static int da9055_adc_manual_read(struct da9055_hwmon *hwmon,
  56                                        unsigned char channel)
  57{
  58        int ret;
  59        unsigned short calc_data;
  60        unsigned short data;
  61        unsigned char mux_sel;
  62        struct da9055 *da9055 = hwmon->da9055;
  63
  64        if (channel > DA9055_ADC_TJUNC)
  65                return -EINVAL;
  66
  67        mutex_lock(&hwmon->irq_lock);
  68
  69        /* Selects desired MUX for manual conversion */
  70        mux_sel = chan_mux[channel] | DA9055_ADC_MAN_CONV;
  71
  72        ret = da9055_reg_write(da9055, DA9055_REG_ADC_MAN, mux_sel);
  73        if (ret < 0)
  74                goto err;
  75
  76        /* Wait for an interrupt */
  77        if (!wait_for_completion_timeout(&hwmon->done,
  78                                        msecs_to_jiffies(500))) {
  79                dev_err(da9055->dev,
  80                        "timeout waiting for ADC conversion interrupt\n");
  81                ret = -ETIMEDOUT;
  82                goto err;
  83        }
  84
  85        ret = da9055_reg_read(da9055, DA9055_REG_ADC_RES_H);
  86        if (ret < 0)
  87                goto err;
  88
  89        calc_data = (unsigned short)ret;
  90        data = calc_data << 2;
  91
  92        ret = da9055_reg_read(da9055, DA9055_REG_ADC_RES_L);
  93        if (ret < 0)
  94                goto err;
  95
  96        calc_data = (unsigned short)(ret & DA9055_ADC_LSB_MASK);
  97        data |= calc_data;
  98
  99        ret = data;
 100
 101err:
 102        mutex_unlock(&hwmon->irq_lock);
 103        return ret;
 104}
 105
 106static irqreturn_t da9055_auxadc_irq(int irq, void *irq_data)
 107{
 108        struct da9055_hwmon *hwmon = irq_data;
 109
 110        complete(&hwmon->done);
 111
 112        return IRQ_HANDLED;
 113}
 114
 115/* Conversion function for VSYS and ADCINx */
 116static inline int volt_reg_to_mv(int value, int channel)
 117{
 118        if (channel == DA9055_ADC_VSYS)
 119                return DIV_ROUND_CLOSEST(value * 1000, DA9055_VSYS_DIV) + 2500;
 120        else
 121                return DIV_ROUND_CLOSEST(value * 1000, DA9055_ADCIN_DIV);
 122}
 123
 124static int da9055_enable_auto_mode(struct da9055 *da9055, int channel)
 125{
 126
 127        return da9055_reg_update(da9055, DA9055_REG_ADC_CONT, 1 << channel,
 128                                1 << channel);
 129
 130}
 131
 132static int da9055_disable_auto_mode(struct da9055 *da9055, int channel)
 133{
 134
 135        return da9055_reg_update(da9055, DA9055_REG_ADC_CONT, 1 << channel, 0);
 136}
 137
 138static ssize_t da9055_auto_ch_show(struct device *dev,
 139                                   struct device_attribute *devattr,
 140                                   char *buf)
 141{
 142        struct da9055_hwmon *hwmon = dev_get_drvdata(dev);
 143        int ret, adc;
 144        int channel = to_sensor_dev_attr(devattr)->index;
 145
 146        mutex_lock(&hwmon->hwmon_lock);
 147
 148        ret = da9055_enable_auto_mode(hwmon->da9055, channel);
 149        if (ret < 0)
 150                goto hwmon_err;
 151
 152        usleep_range(10000, 10500);
 153
 154        adc = da9055_reg_read(hwmon->da9055, DA9055_REG_VSYS_RES + channel);
 155        if (adc < 0) {
 156                ret = adc;
 157                goto hwmon_err_release;
 158        }
 159
 160        ret = da9055_disable_auto_mode(hwmon->da9055, channel);
 161        if (ret < 0)
 162                goto hwmon_err;
 163
 164        mutex_unlock(&hwmon->hwmon_lock);
 165
 166        return sprintf(buf, "%d\n", volt_reg_to_mv(adc, channel));
 167
 168hwmon_err_release:
 169        da9055_disable_auto_mode(hwmon->da9055, channel);
 170hwmon_err:
 171        mutex_unlock(&hwmon->hwmon_lock);
 172        return ret;
 173}
 174
 175static ssize_t da9055_tjunc_show(struct device *dev,
 176                                 struct device_attribute *devattr, char *buf)
 177{
 178        struct da9055_hwmon *hwmon = dev_get_drvdata(dev);
 179        int tjunc;
 180        int toffset;
 181
 182        tjunc = da9055_adc_manual_read(hwmon, DA9055_ADC_TJUNC);
 183        if (tjunc < 0)
 184                return tjunc;
 185
 186        toffset = da9055_reg_read(hwmon->da9055, DA9055_REG_T_OFFSET);
 187        if (toffset < 0)
 188                return toffset;
 189
 190        /*
 191         * Degrees celsius = -0.4084 * (ADC_RES - T_OFFSET) + 307.6332
 192         * T_OFFSET is a trim value used to improve accuracy of the result
 193         */
 194        return sprintf(buf, "%d\n", DIV_ROUND_CLOSEST(-4084 * (tjunc - toffset)
 195                                                        + 3076332, 10000));
 196}
 197
 198static ssize_t label_show(struct device *dev,
 199                          struct device_attribute *devattr, char *buf)
 200{
 201        return sprintf(buf, "%s\n",
 202                       input_names[to_sensor_dev_attr(devattr)->index]);
 203}
 204
 205static SENSOR_DEVICE_ATTR_RO(in0_input, da9055_auto_ch, DA9055_ADC_VSYS);
 206static SENSOR_DEVICE_ATTR_RO(in0_label, label, DA9055_ADC_VSYS);
 207static SENSOR_DEVICE_ATTR_RO(in1_input, da9055_auto_ch, DA9055_ADC_ADCIN1);
 208static SENSOR_DEVICE_ATTR_RO(in1_label, label, DA9055_ADC_ADCIN1);
 209static SENSOR_DEVICE_ATTR_RO(in2_input, da9055_auto_ch, DA9055_ADC_ADCIN2);
 210static SENSOR_DEVICE_ATTR_RO(in2_label, label, DA9055_ADC_ADCIN2);
 211static SENSOR_DEVICE_ATTR_RO(in3_input, da9055_auto_ch, DA9055_ADC_ADCIN3);
 212static SENSOR_DEVICE_ATTR_RO(in3_label, label, DA9055_ADC_ADCIN3);
 213
 214static SENSOR_DEVICE_ATTR_RO(temp1_input, da9055_tjunc, DA9055_ADC_TJUNC);
 215static SENSOR_DEVICE_ATTR_RO(temp1_label, label, DA9055_ADC_TJUNC);
 216
 217static struct attribute *da9055_attrs[] = {
 218        &sensor_dev_attr_in0_input.dev_attr.attr,
 219        &sensor_dev_attr_in0_label.dev_attr.attr,
 220        &sensor_dev_attr_in1_input.dev_attr.attr,
 221        &sensor_dev_attr_in1_label.dev_attr.attr,
 222        &sensor_dev_attr_in2_input.dev_attr.attr,
 223        &sensor_dev_attr_in2_label.dev_attr.attr,
 224        &sensor_dev_attr_in3_input.dev_attr.attr,
 225        &sensor_dev_attr_in3_label.dev_attr.attr,
 226
 227        &sensor_dev_attr_temp1_input.dev_attr.attr,
 228        &sensor_dev_attr_temp1_label.dev_attr.attr,
 229        NULL
 230};
 231
 232ATTRIBUTE_GROUPS(da9055);
 233
 234static int da9055_hwmon_probe(struct platform_device *pdev)
 235{
 236        struct device *dev = &pdev->dev;
 237        struct da9055_hwmon *hwmon;
 238        struct device *hwmon_dev;
 239        int hwmon_irq, ret;
 240
 241        hwmon = devm_kzalloc(dev, sizeof(struct da9055_hwmon), GFP_KERNEL);
 242        if (!hwmon)
 243                return -ENOMEM;
 244
 245        mutex_init(&hwmon->hwmon_lock);
 246        mutex_init(&hwmon->irq_lock);
 247
 248        init_completion(&hwmon->done);
 249        hwmon->da9055 = dev_get_drvdata(pdev->dev.parent);
 250
 251        hwmon_irq = platform_get_irq_byname(pdev, "HWMON");
 252        if (hwmon_irq < 0)
 253                return hwmon_irq;
 254
 255        ret = devm_request_threaded_irq(&pdev->dev, hwmon_irq,
 256                                        NULL, da9055_auxadc_irq,
 257                                        IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
 258                                        "adc-irq", hwmon);
 259        if (ret != 0) {
 260                dev_err(hwmon->da9055->dev, "DA9055 ADC IRQ failed ret=%d\n",
 261                        ret);
 262                return ret;
 263        }
 264
 265        hwmon_dev = devm_hwmon_device_register_with_groups(dev, "da9055",
 266                                                           hwmon,
 267                                                           da9055_groups);
 268        return PTR_ERR_OR_ZERO(hwmon_dev);
 269}
 270
 271static struct platform_driver da9055_hwmon_driver = {
 272        .probe = da9055_hwmon_probe,
 273        .driver = {
 274                .name = "da9055-hwmon",
 275        },
 276};
 277
 278module_platform_driver(da9055_hwmon_driver);
 279
 280MODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>");
 281MODULE_DESCRIPTION("DA9055 HWMON driver");
 282MODULE_LICENSE("GPL");
 283MODULE_ALIAS("platform:da9055-hwmon");
 284