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 device   *class_device;
  40        struct mutex    hwmon_lock;
  41        struct mutex    irq_lock;
  42        struct completion done;
  43};
  44
  45static const char * const input_names[] = {
  46        [DA9055_ADC_VSYS]       = "VSYS",
  47        [DA9055_ADC_ADCIN1]     = "ADC IN1",
  48        [DA9055_ADC_ADCIN2]     = "ADC IN2",
  49        [DA9055_ADC_ADCIN3]     = "ADC IN3",
  50        [DA9055_ADC_TJUNC]      = "CHIP TEMP",
  51};
  52
  53static const u8 chan_mux[DA9055_ADC_TJUNC + 1] = {
  54        [DA9055_ADC_VSYS]       = DA9055_ADC_MUX_VSYS,
  55        [DA9055_ADC_ADCIN1]     = DA9055_ADC_MUX_ADCIN1,
  56        [DA9055_ADC_ADCIN2]     = DA9055_ADC_MUX_ADCIN2,
  57        [DA9055_ADC_ADCIN3]     = DA9055_ADC_MUX_ADCIN3,
  58        [DA9055_ADC_TJUNC]      = DA9055_ADC_MUX_T_SENSE,
  59};
  60
  61static int da9055_adc_manual_read(struct da9055_hwmon *hwmon,
  62                                        unsigned char channel)
  63{
  64        int ret;
  65        unsigned short calc_data;
  66        unsigned short data;
  67        unsigned char mux_sel;
  68        struct da9055 *da9055 = hwmon->da9055;
  69
  70        if (channel > DA9055_ADC_TJUNC)
  71                return -EINVAL;
  72
  73        mutex_lock(&hwmon->irq_lock);
  74
  75        /* Selects desired MUX for manual conversion */
  76        mux_sel = chan_mux[channel] | DA9055_ADC_MAN_CONV;
  77
  78        ret = da9055_reg_write(da9055, DA9055_REG_ADC_MAN, mux_sel);
  79        if (ret < 0)
  80                goto err;
  81
  82        /* Wait for an interrupt */
  83        if (!wait_for_completion_timeout(&hwmon->done,
  84                                        msecs_to_jiffies(500))) {
  85                dev_err(da9055->dev,
  86                        "timeout waiting for ADC conversion interrupt\n");
  87                ret = -ETIMEDOUT;
  88                goto err;
  89        }
  90
  91        ret = da9055_reg_read(da9055, DA9055_REG_ADC_RES_H);
  92        if (ret < 0)
  93                goto err;
  94
  95        calc_data = (unsigned short)ret;
  96        data = calc_data << 2;
  97
  98        ret = da9055_reg_read(da9055, DA9055_REG_ADC_RES_L);
  99        if (ret < 0)
 100                goto err;
 101
 102        calc_data = (unsigned short)(ret & DA9055_ADC_LSB_MASK);
 103        data |= calc_data;
 104
 105        ret = data;
 106
 107err:
 108        mutex_unlock(&hwmon->irq_lock);
 109        return ret;
 110}
 111
 112static irqreturn_t da9055_auxadc_irq(int irq, void *irq_data)
 113{
 114        struct da9055_hwmon *hwmon = irq_data;
 115
 116        complete(&hwmon->done);
 117
 118        return IRQ_HANDLED;
 119}
 120
 121/* Conversion function for VSYS and ADCINx */
 122static inline int volt_reg_to_mv(int value, int channel)
 123{
 124        if (channel == DA9055_ADC_VSYS)
 125                return DIV_ROUND_CLOSEST(value * 1000, DA9055_VSYS_DIV) + 2500;
 126        else
 127                return DIV_ROUND_CLOSEST(value * 1000, DA9055_ADCIN_DIV);
 128}
 129
 130static int da9055_enable_auto_mode(struct da9055 *da9055, int channel)
 131{
 132
 133        return da9055_reg_update(da9055, DA9055_REG_ADC_CONT, 1 << channel,
 134                                1 << channel);
 135
 136}
 137
 138static int da9055_disable_auto_mode(struct da9055 *da9055, int channel)
 139{
 140
 141        return da9055_reg_update(da9055, DA9055_REG_ADC_CONT, 1 << channel, 0);
 142}
 143
 144static ssize_t da9055_read_auto_ch(struct device *dev,
 145                                struct device_attribute *devattr, char *buf)
 146{
 147        struct da9055_hwmon *hwmon = dev_get_drvdata(dev);
 148        int ret, adc;
 149        int channel = to_sensor_dev_attr(devattr)->index;
 150
 151        mutex_lock(&hwmon->hwmon_lock);
 152
 153        ret = da9055_enable_auto_mode(hwmon->da9055, channel);
 154        if (ret < 0)
 155                goto hwmon_err;
 156
 157        usleep_range(10000, 10500);
 158
 159        adc = da9055_reg_read(hwmon->da9055, DA9055_REG_VSYS_RES + channel);
 160        if (adc < 0) {
 161                ret = adc;
 162                goto hwmon_err_release;
 163        }
 164
 165        ret = da9055_disable_auto_mode(hwmon->da9055, channel);
 166        if (ret < 0)
 167                goto hwmon_err;
 168
 169        mutex_unlock(&hwmon->hwmon_lock);
 170
 171        return sprintf(buf, "%d\n", volt_reg_to_mv(adc, channel));
 172
 173hwmon_err_release:
 174        da9055_disable_auto_mode(hwmon->da9055, channel);
 175hwmon_err:
 176        mutex_unlock(&hwmon->hwmon_lock);
 177        return ret;
 178}
 179
 180static ssize_t da9055_read_tjunc(struct device *dev,
 181                                 struct device_attribute *devattr, char *buf)
 182{
 183        struct da9055_hwmon *hwmon = dev_get_drvdata(dev);
 184        int tjunc;
 185        int toffset;
 186
 187        tjunc = da9055_adc_manual_read(hwmon, DA9055_ADC_TJUNC);
 188        if (tjunc < 0)
 189                return tjunc;
 190
 191        toffset = da9055_reg_read(hwmon->da9055, DA9055_REG_T_OFFSET);
 192        if (toffset < 0)
 193                return toffset;
 194
 195        /*
 196         * Degrees celsius = -0.4084 * (ADC_RES - T_OFFSET) + 307.6332
 197         * T_OFFSET is a trim value used to improve accuracy of the result
 198         */
 199        return sprintf(buf, "%d\n", DIV_ROUND_CLOSEST(-4084 * (tjunc - toffset)
 200                                                        + 3076332, 10000));
 201}
 202
 203static ssize_t da9055_hwmon_show_name(struct device *dev,
 204                                      struct device_attribute *devattr,
 205                                      char *buf)
 206{
 207        return sprintf(buf, "da9055-hwmon\n");
 208}
 209
 210static ssize_t show_label(struct device *dev,
 211                          struct device_attribute *devattr, char *buf)
 212{
 213        return sprintf(buf, "%s\n",
 214                       input_names[to_sensor_dev_attr(devattr)->index]);
 215}
 216
 217static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, da9055_read_auto_ch, NULL,
 218                          DA9055_ADC_VSYS);
 219static SENSOR_DEVICE_ATTR(in0_label, S_IRUGO, show_label, NULL,
 220                          DA9055_ADC_VSYS);
 221static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, da9055_read_auto_ch, NULL,
 222                          DA9055_ADC_ADCIN1);
 223static SENSOR_DEVICE_ATTR(in1_label, S_IRUGO, show_label, NULL,
 224                          DA9055_ADC_ADCIN1);
 225static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, da9055_read_auto_ch, NULL,
 226                          DA9055_ADC_ADCIN2);
 227static SENSOR_DEVICE_ATTR(in2_label, S_IRUGO, show_label, NULL,
 228                          DA9055_ADC_ADCIN2);
 229static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, da9055_read_auto_ch, NULL,
 230                          DA9055_ADC_ADCIN3);
 231static SENSOR_DEVICE_ATTR(in3_label, S_IRUGO, show_label, NULL,
 232                          DA9055_ADC_ADCIN3);
 233
 234static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, da9055_read_tjunc, NULL,
 235                          DA9055_ADC_TJUNC);
 236static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_label, NULL,
 237                          DA9055_ADC_TJUNC);
 238
 239static DEVICE_ATTR(name, S_IRUGO, da9055_hwmon_show_name, NULL);
 240
 241static struct attribute *da9055_attr[] = {
 242        &dev_attr_name.attr,
 243        &sensor_dev_attr_in0_input.dev_attr.attr,
 244        &sensor_dev_attr_in0_label.dev_attr.attr,
 245        &sensor_dev_attr_in1_input.dev_attr.attr,
 246        &sensor_dev_attr_in1_label.dev_attr.attr,
 247        &sensor_dev_attr_in2_input.dev_attr.attr,
 248        &sensor_dev_attr_in2_label.dev_attr.attr,
 249        &sensor_dev_attr_in3_input.dev_attr.attr,
 250        &sensor_dev_attr_in3_label.dev_attr.attr,
 251
 252        &sensor_dev_attr_temp1_input.dev_attr.attr,
 253        &sensor_dev_attr_temp1_label.dev_attr.attr,
 254        NULL
 255};
 256
 257static const struct attribute_group da9055_attr_group = {.attrs = da9055_attr};
 258
 259static int da9055_hwmon_probe(struct platform_device *pdev)
 260{
 261        struct da9055_hwmon *hwmon;
 262        int hwmon_irq, ret;
 263
 264        hwmon = devm_kzalloc(&pdev->dev, sizeof(struct da9055_hwmon),
 265                             GFP_KERNEL);
 266        if (!hwmon)
 267                return -ENOMEM;
 268
 269        mutex_init(&hwmon->hwmon_lock);
 270        mutex_init(&hwmon->irq_lock);
 271
 272        init_completion(&hwmon->done);
 273        hwmon->da9055 = dev_get_drvdata(pdev->dev.parent);
 274
 275        platform_set_drvdata(pdev, hwmon);
 276
 277        hwmon_irq = platform_get_irq_byname(pdev, "HWMON");
 278        if (hwmon_irq < 0)
 279                return hwmon_irq;
 280
 281        hwmon_irq = regmap_irq_get_virq(hwmon->da9055->irq_data, hwmon_irq);
 282        if (hwmon_irq < 0)
 283                return hwmon_irq;
 284
 285        ret = devm_request_threaded_irq(&pdev->dev, hwmon_irq,
 286                                        NULL, da9055_auxadc_irq,
 287                                        IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
 288                                        "adc-irq", hwmon);
 289        if (ret != 0) {
 290                dev_err(hwmon->da9055->dev, "DA9055 ADC IRQ failed ret=%d\n",
 291                        ret);
 292                return ret;
 293        }
 294
 295        ret = sysfs_create_group(&pdev->dev.kobj, &da9055_attr_group);
 296        if (ret)
 297                return ret;
 298
 299        hwmon->class_device = hwmon_device_register(&pdev->dev);
 300        if (IS_ERR(hwmon->class_device)) {
 301                ret = PTR_ERR(hwmon->class_device);
 302                goto err;
 303        }
 304
 305        return 0;
 306
 307err:
 308        sysfs_remove_group(&pdev->dev.kobj, &da9055_attr_group);
 309        return ret;
 310}
 311
 312static int da9055_hwmon_remove(struct platform_device *pdev)
 313{
 314        struct da9055_hwmon *hwmon = platform_get_drvdata(pdev);
 315
 316        sysfs_remove_group(&pdev->dev.kobj, &da9055_attr_group);
 317        hwmon_device_unregister(hwmon->class_device);
 318
 319        return 0;
 320}
 321
 322static struct platform_driver da9055_hwmon_driver = {
 323        .probe = da9055_hwmon_probe,
 324        .remove = da9055_hwmon_remove,
 325        .driver = {
 326                .name = "da9055-hwmon",
 327                .owner = THIS_MODULE,
 328        },
 329};
 330
 331module_platform_driver(da9055_hwmon_driver);
 332
 333MODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>");
 334MODULE_DESCRIPTION("DA9055 HWMON driver");
 335MODULE_LICENSE("GPL");
 336MODULE_ALIAS("platform:da9055-hwmon");
 337