linux/drivers/hwmon/s3c-hwmon.c
<<
>>
Prefs
   1/* linux/drivers/hwmon/s3c-hwmon.c
   2 *
   3 * Copyright (C) 2005, 2008, 2009 Simtec Electronics
   4 *      http://armlinux.simtec.co.uk/
   5 *      Ben Dooks <ben@simtec.co.uk>
   6 *
   7 * S3C24XX/S3C64XX ADC hwmon support
   8 *
   9 * This program is free software; you can redistribute it and/or modify
  10 * it under the terms of the GNU General Public License version 2 as
  11 * published by the Free Software Foundation.
  12 *
  13 * This program is distributed in the hope that it will be useful,
  14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16 * GNU General Public License for more details.
  17 *
  18 * You should have received a copy of the GNU General Public License
  19 * along with this program; if not, write to the Free Software
  20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  21*/
  22
  23#include <linux/module.h>
  24#include <linux/slab.h>
  25#include <linux/delay.h>
  26#include <linux/io.h>
  27#include <linux/init.h>
  28#include <linux/err.h>
  29#include <linux/clk.h>
  30#include <linux/interrupt.h>
  31#include <linux/platform_device.h>
  32
  33#include <linux/hwmon.h>
  34#include <linux/hwmon-sysfs.h>
  35
  36#include <plat/adc.h>
  37#include <plat/hwmon.h>
  38
  39struct s3c_hwmon_attr {
  40        struct sensor_device_attribute  in;
  41        struct sensor_device_attribute  label;
  42        char                            in_name[12];
  43        char                            label_name[12];
  44};
  45
  46/**
  47 * struct s3c_hwmon - ADC hwmon client information
  48 * @lock: Access lock to serialise the conversions.
  49 * @client: The client we registered with the S3C ADC core.
  50 * @hwmon_dev: The hwmon device we created.
  51 * @attr: The holders for the channel attributes.
  52*/
  53struct s3c_hwmon {
  54        struct semaphore        lock;
  55        struct s3c_adc_client   *client;
  56        struct device           *hwmon_dev;
  57
  58        struct s3c_hwmon_attr   attrs[8];
  59};
  60
  61/**
  62 * s3c_hwmon_read_ch - read a value from a given adc channel.
  63 * @dev: The device.
  64 * @hwmon: Our state.
  65 * @channel: The channel we're reading from.
  66 *
  67 * Read a value from the @channel with the proper locking and sleep until
  68 * either the read completes or we timeout awaiting the ADC core to get
  69 * back to us.
  70 */
  71static int s3c_hwmon_read_ch(struct device *dev,
  72                             struct s3c_hwmon *hwmon, int channel)
  73{
  74        int ret;
  75
  76        ret = down_interruptible(&hwmon->lock);
  77        if (ret < 0)
  78                return ret;
  79
  80        dev_dbg(dev, "reading channel %d\n", channel);
  81
  82        ret = s3c_adc_read(hwmon->client, channel);
  83        up(&hwmon->lock);
  84
  85        return ret;
  86}
  87
  88#ifdef CONFIG_SENSORS_S3C_RAW
  89/**
  90 * s3c_hwmon_show_raw - show a conversion from the raw channel number.
  91 * @dev: The device that the attribute belongs to.
  92 * @attr: The attribute being read.
  93 * @buf: The result buffer.
  94 *
  95 * This show deals with the raw attribute, registered for each possible
  96 * ADC channel. This does a conversion and returns the raw (un-scaled)
  97 * value returned from the hardware.
  98 */
  99static ssize_t s3c_hwmon_show_raw(struct device *dev,
 100                                  struct device_attribute *attr, char *buf)
 101{
 102        struct s3c_hwmon *adc = platform_get_drvdata(to_platform_device(dev));
 103        struct sensor_device_attribute *sa = to_sensor_dev_attr(attr);
 104        int ret;
 105
 106        ret = s3c_hwmon_read_ch(dev, adc, sa->index);
 107
 108        return  (ret < 0) ? ret : snprintf(buf, PAGE_SIZE, "%d\n", ret);
 109}
 110
 111#define DEF_ADC_ATTR(x) \
 112        static SENSOR_DEVICE_ATTR(adc##x##_raw, S_IRUGO, s3c_hwmon_show_raw, NULL, x)
 113
 114DEF_ADC_ATTR(0);
 115DEF_ADC_ATTR(1);
 116DEF_ADC_ATTR(2);
 117DEF_ADC_ATTR(3);
 118DEF_ADC_ATTR(4);
 119DEF_ADC_ATTR(5);
 120DEF_ADC_ATTR(6);
 121DEF_ADC_ATTR(7);
 122
 123static struct attribute *s3c_hwmon_attrs[9] = {
 124        &sensor_dev_attr_adc0_raw.dev_attr.attr,
 125        &sensor_dev_attr_adc1_raw.dev_attr.attr,
 126        &sensor_dev_attr_adc2_raw.dev_attr.attr,
 127        &sensor_dev_attr_adc3_raw.dev_attr.attr,
 128        &sensor_dev_attr_adc4_raw.dev_attr.attr,
 129        &sensor_dev_attr_adc5_raw.dev_attr.attr,
 130        &sensor_dev_attr_adc6_raw.dev_attr.attr,
 131        &sensor_dev_attr_adc7_raw.dev_attr.attr,
 132        NULL,
 133};
 134
 135static struct attribute_group s3c_hwmon_attrgroup = {
 136        .attrs  = s3c_hwmon_attrs,
 137};
 138
 139static inline int s3c_hwmon_add_raw(struct device *dev)
 140{
 141        return sysfs_create_group(&dev->kobj, &s3c_hwmon_attrgroup);
 142}
 143
 144static inline void s3c_hwmon_remove_raw(struct device *dev)
 145{
 146        sysfs_remove_group(&dev->kobj, &s3c_hwmon_attrgroup);
 147}
 148
 149#else
 150
 151static inline int s3c_hwmon_add_raw(struct device *dev) { return 0; }
 152static inline void s3c_hwmon_remove_raw(struct device *dev) { }
 153
 154#endif /* CONFIG_SENSORS_S3C_RAW */
 155
 156/**
 157 * s3c_hwmon_ch_show - show value of a given channel
 158 * @dev: The device that the attribute belongs to.
 159 * @attr: The attribute being read.
 160 * @buf: The result buffer.
 161 *
 162 * Read a value from the ADC and scale it before returning it to the
 163 * caller. The scale factor is gained from the channel configuration
 164 * passed via the platform data when the device was registered.
 165 */
 166static ssize_t s3c_hwmon_ch_show(struct device *dev,
 167                                 struct device_attribute *attr,
 168                                 char *buf)
 169{
 170        struct sensor_device_attribute *sen_attr = to_sensor_dev_attr(attr);
 171        struct s3c_hwmon *hwmon = platform_get_drvdata(to_platform_device(dev));
 172        struct s3c_hwmon_pdata *pdata = dev->platform_data;
 173        struct s3c_hwmon_chcfg *cfg;
 174        int ret;
 175
 176        cfg = pdata->in[sen_attr->index];
 177
 178        ret = s3c_hwmon_read_ch(dev, hwmon, sen_attr->index);
 179        if (ret < 0)
 180                return ret;
 181
 182        ret *= cfg->mult;
 183        ret = DIV_ROUND_CLOSEST(ret, cfg->div);
 184
 185        return snprintf(buf, PAGE_SIZE, "%d\n", ret);
 186}
 187
 188/**
 189 * s3c_hwmon_label_show - show label name of the given channel.
 190 * @dev: The device that the attribute belongs to.
 191 * @attr: The attribute being read.
 192 * @buf: The result buffer.
 193 *
 194 * Return the label name of a given channel
 195 */
 196static ssize_t s3c_hwmon_label_show(struct device *dev,
 197                                    struct device_attribute *attr,
 198                                    char *buf)
 199{
 200        struct sensor_device_attribute *sen_attr = to_sensor_dev_attr(attr);
 201        struct s3c_hwmon_pdata *pdata = dev->platform_data;
 202        struct s3c_hwmon_chcfg *cfg;
 203
 204        cfg = pdata->in[sen_attr->index];
 205
 206        return snprintf(buf, PAGE_SIZE, "%s\n", cfg->name);
 207}
 208
 209/**
 210 * s3c_hwmon_create_attr - create hwmon attribute for given channel.
 211 * @dev: The device to create the attribute on.
 212 * @cfg: The channel configuration passed from the platform data.
 213 * @channel: The ADC channel number to process.
 214 *
 215 * Create the scaled attribute for use with hwmon from the specified
 216 * platform data in @pdata. The sysfs entry is handled by the routine
 217 * s3c_hwmon_ch_show().
 218 *
 219 * The attribute name is taken from the configuration data if present
 220 * otherwise the name is taken by concatenating in_ with the channel
 221 * number.
 222 */
 223static int s3c_hwmon_create_attr(struct device *dev,
 224                                 struct s3c_hwmon_chcfg *cfg,
 225                                 struct s3c_hwmon_attr *attrs,
 226                                 int channel)
 227{
 228        struct sensor_device_attribute *attr;
 229        int ret;
 230
 231        snprintf(attrs->in_name, sizeof(attrs->in_name), "in%d_input", channel);
 232
 233        attr = &attrs->in;
 234        attr->index = channel;
 235        attr->dev_attr.attr.name  = attrs->in_name;
 236        attr->dev_attr.attr.mode  = S_IRUGO;
 237        attr->dev_attr.attr.owner = THIS_MODULE;
 238        attr->dev_attr.show = s3c_hwmon_ch_show;
 239
 240        ret =  device_create_file(dev, &attr->dev_attr);
 241        if (ret < 0) {
 242                dev_err(dev, "failed to create input attribute\n");
 243                return ret;
 244        }
 245
 246        /* if this has a name, add a label */
 247        if (cfg->name) {
 248                snprintf(attrs->label_name, sizeof(attrs->label_name),
 249                         "in%d_label", channel);
 250
 251                attr = &attrs->label;
 252                attr->index = channel;
 253                attr->dev_attr.attr.name  = attrs->label_name;
 254                attr->dev_attr.attr.mode  = S_IRUGO;
 255                attr->dev_attr.attr.owner = THIS_MODULE;
 256                attr->dev_attr.show = s3c_hwmon_label_show;
 257
 258                ret = device_create_file(dev, &attr->dev_attr);
 259                if (ret < 0) {
 260                        device_remove_file(dev, &attrs->in.dev_attr);
 261                        dev_err(dev, "failed to create label attribute\n");
 262                }
 263        }
 264
 265        return ret;
 266}
 267
 268static void s3c_hwmon_remove_attr(struct device *dev,
 269                                  struct s3c_hwmon_attr *attrs)
 270{
 271        device_remove_file(dev, &attrs->in.dev_attr);
 272        device_remove_file(dev, &attrs->label.dev_attr);
 273}
 274
 275/**
 276 * s3c_hwmon_probe - device probe entry.
 277 * @dev: The device being probed.
 278*/
 279static int __devinit s3c_hwmon_probe(struct platform_device *dev)
 280{
 281        struct s3c_hwmon_pdata *pdata = dev->dev.platform_data;
 282        struct s3c_hwmon *hwmon;
 283        int ret = 0;
 284        int i;
 285
 286        if (!pdata) {
 287                dev_err(&dev->dev, "no platform data supplied\n");
 288                return -EINVAL;
 289        }
 290
 291        hwmon = kzalloc(sizeof(struct s3c_hwmon), GFP_KERNEL);
 292        if (hwmon == NULL) {
 293                dev_err(&dev->dev, "no memory\n");
 294                return -ENOMEM;
 295        }
 296
 297        platform_set_drvdata(dev, hwmon);
 298
 299        init_MUTEX(&hwmon->lock);
 300
 301        /* Register with the core ADC driver. */
 302
 303        hwmon->client = s3c_adc_register(dev, NULL, NULL, 0);
 304        if (IS_ERR(hwmon->client)) {
 305                dev_err(&dev->dev, "cannot register adc\n");
 306                ret = PTR_ERR(hwmon->client);
 307                goto err_mem;
 308        }
 309
 310        /* add attributes for our adc devices. */
 311
 312        ret = s3c_hwmon_add_raw(&dev->dev);
 313        if (ret)
 314                goto err_registered;
 315
 316        /* register with the hwmon core */
 317
 318        hwmon->hwmon_dev = hwmon_device_register(&dev->dev);
 319        if (IS_ERR(hwmon->hwmon_dev)) {
 320                dev_err(&dev->dev, "error registering with hwmon\n");
 321                ret = PTR_ERR(hwmon->hwmon_dev);
 322                goto err_raw_attribute;
 323        }
 324
 325        for (i = 0; i < ARRAY_SIZE(pdata->in); i++) {
 326                struct s3c24xx_adc_hwmon_incfg *cfg = pdata->in[i];
 327
 328                if (!cfg)
 329                        continue;
 330
 331                if (cfg->mult >= 0x10000)
 332                        dev_warn(&dev->dev,
 333                                 "channel %d multiplier too large\n",
 334                                 i);
 335
 336                if (cfg->divider == 0) {
 337                        dev_err(&dev->dev, "channel %d divider zero\n", i);
 338                        continue;
 339                }
 340
 341                ret = s3c_hwmon_create_attr(&dev->dev, pdata->in[i],
 342                                            &hwmon->attrs[i], i);
 343                if (ret) {
 344                        dev_err(&dev->dev,
 345                                        "error creating channel %d\n", i);
 346
 347                        for (i--; i >= 0; i--)
 348                                s3c_hwmon_remove_attr(&dev->dev,
 349                                                              &hwmon->attrs[i]);
 350
 351                        goto err_hwmon_register;
 352                }
 353        }
 354
 355        return 0;
 356
 357 err_hwmon_register:
 358        hwmon_device_unregister(hwmon->hwmon_dev);
 359
 360 err_raw_attribute:
 361        s3c_hwmon_remove_raw(&dev->dev);
 362
 363 err_registered:
 364        s3c_adc_release(hwmon->client);
 365
 366 err_mem:
 367        kfree(hwmon);
 368        return ret;
 369}
 370
 371static int __devexit s3c_hwmon_remove(struct platform_device *dev)
 372{
 373        struct s3c_hwmon *hwmon = platform_get_drvdata(dev);
 374        int i;
 375
 376        s3c_hwmon_remove_raw(&dev->dev);
 377
 378        for (i = 0; i < ARRAY_SIZE(hwmon->attrs); i++)
 379                s3c_hwmon_remove_attr(&dev->dev, &hwmon->attrs[i]);
 380
 381        hwmon_device_unregister(hwmon->hwmon_dev);
 382        s3c_adc_release(hwmon->client);
 383
 384        return 0;
 385}
 386
 387static struct platform_driver s3c_hwmon_driver = {
 388        .driver = {
 389                .name           = "s3c-hwmon",
 390                .owner          = THIS_MODULE,
 391        },
 392        .probe          = s3c_hwmon_probe,
 393        .remove         = __devexit_p(s3c_hwmon_remove),
 394};
 395
 396static int __init s3c_hwmon_init(void)
 397{
 398        return platform_driver_register(&s3c_hwmon_driver);
 399}
 400
 401static void __exit s3c_hwmon_exit(void)
 402{
 403        platform_driver_unregister(&s3c_hwmon_driver);
 404}
 405
 406module_init(s3c_hwmon_init);
 407module_exit(s3c_hwmon_exit);
 408
 409MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
 410MODULE_DESCRIPTION("S3C ADC HWMon driver");
 411MODULE_LICENSE("GPL v2");
 412MODULE_ALIAS("platform:s3c-hwmon");
 413