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