linux/drivers/hwmon/ntc_thermistor.c
<<
>>
Prefs
   1/*
   2 * ntc_thermistor.c - NTC Thermistors
   3 *
   4 *  Copyright (C) 2010 Samsung Electronics
   5 *  MyungJoo Ham <myungjoo.ham@samsung.com>
   6 *
   7 * This program is free software; you can redistribute it and/or modify
   8 * it under the terms of the GNU General Public License as published by
   9 * the Free Software Foundation; either version 2 of the License, or
  10 * (at your option) any later version.
  11 *
  12 * This program is distributed in the hope that it will be useful,
  13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15 * GNU General Public License for more details.
  16 *
  17 * You should have received a copy of the GNU General Public License
  18 * along with this program; if not, write to the Free Software
  19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  20 *
  21 */
  22
  23#include <linux/slab.h>
  24#include <linux/module.h>
  25#include <linux/pm_runtime.h>
  26#include <linux/math64.h>
  27#include <linux/platform_device.h>
  28#include <linux/err.h>
  29
  30#include <linux/platform_data/ntc_thermistor.h>
  31
  32#include <linux/hwmon.h>
  33#include <linux/hwmon-sysfs.h>
  34
  35struct ntc_compensation {
  36        int             temp_C;
  37        unsigned int    ohm;
  38};
  39
  40/*
  41 * A compensation table should be sorted by the values of .ohm
  42 * in descending order.
  43 * The following compensation tables are from the specification of Murata NTC
  44 * Thermistors Datasheet
  45 */
  46const struct ntc_compensation ncpXXwb473[] = {
  47        { .temp_C       = -40, .ohm     = 1747920 },
  48        { .temp_C       = -35, .ohm     = 1245428 },
  49        { .temp_C       = -30, .ohm     = 898485 },
  50        { .temp_C       = -25, .ohm     = 655802 },
  51        { .temp_C       = -20, .ohm     = 483954 },
  52        { .temp_C       = -15, .ohm     = 360850 },
  53        { .temp_C       = -10, .ohm     = 271697 },
  54        { .temp_C       = -5, .ohm      = 206463 },
  55        { .temp_C       = 0, .ohm       = 158214 },
  56        { .temp_C       = 5, .ohm       = 122259 },
  57        { .temp_C       = 10, .ohm      = 95227 },
  58        { .temp_C       = 15, .ohm      = 74730 },
  59        { .temp_C       = 20, .ohm      = 59065 },
  60        { .temp_C       = 25, .ohm      = 47000 },
  61        { .temp_C       = 30, .ohm      = 37643 },
  62        { .temp_C       = 35, .ohm      = 30334 },
  63        { .temp_C       = 40, .ohm      = 24591 },
  64        { .temp_C       = 45, .ohm      = 20048 },
  65        { .temp_C       = 50, .ohm      = 16433 },
  66        { .temp_C       = 55, .ohm      = 13539 },
  67        { .temp_C       = 60, .ohm      = 11209 },
  68        { .temp_C       = 65, .ohm      = 9328 },
  69        { .temp_C       = 70, .ohm      = 7798 },
  70        { .temp_C       = 75, .ohm      = 6544 },
  71        { .temp_C       = 80, .ohm      = 5518 },
  72        { .temp_C       = 85, .ohm      = 4674 },
  73        { .temp_C       = 90, .ohm      = 3972 },
  74        { .temp_C       = 95, .ohm      = 3388 },
  75        { .temp_C       = 100, .ohm     = 2902 },
  76        { .temp_C       = 105, .ohm     = 2494 },
  77        { .temp_C       = 110, .ohm     = 2150 },
  78        { .temp_C       = 115, .ohm     = 1860 },
  79        { .temp_C       = 120, .ohm     = 1615 },
  80        { .temp_C       = 125, .ohm     = 1406 },
  81};
  82const struct ntc_compensation ncpXXwl333[] = {
  83        { .temp_C       = -40, .ohm     = 1610154 },
  84        { .temp_C       = -35, .ohm     = 1130850 },
  85        { .temp_C       = -30, .ohm     = 802609 },
  86        { .temp_C       = -25, .ohm     = 575385 },
  87        { .temp_C       = -20, .ohm     = 416464 },
  88        { .temp_C       = -15, .ohm     = 304219 },
  89        { .temp_C       = -10, .ohm     = 224193 },
  90        { .temp_C       = -5, .ohm      = 166623 },
  91        { .temp_C       = 0, .ohm       = 124850 },
  92        { .temp_C       = 5, .ohm       = 94287 },
  93        { .temp_C       = 10, .ohm      = 71747 },
  94        { .temp_C       = 15, .ohm      = 54996 },
  95        { .temp_C       = 20, .ohm      = 42455 },
  96        { .temp_C       = 25, .ohm      = 33000 },
  97        { .temp_C       = 30, .ohm      = 25822 },
  98        { .temp_C       = 35, .ohm      = 20335 },
  99        { .temp_C       = 40, .ohm      = 16115 },
 100        { .temp_C       = 45, .ohm      = 12849 },
 101        { .temp_C       = 50, .ohm      = 10306 },
 102        { .temp_C       = 55, .ohm      = 8314 },
 103        { .temp_C       = 60, .ohm      = 6746 },
 104        { .temp_C       = 65, .ohm      = 5503 },
 105        { .temp_C       = 70, .ohm      = 4513 },
 106        { .temp_C       = 75, .ohm      = 3721 },
 107        { .temp_C       = 80, .ohm      = 3084 },
 108        { .temp_C       = 85, .ohm      = 2569 },
 109        { .temp_C       = 90, .ohm      = 2151 },
 110        { .temp_C       = 95, .ohm      = 1809 },
 111        { .temp_C       = 100, .ohm     = 1529 },
 112        { .temp_C       = 105, .ohm     = 1299 },
 113        { .temp_C       = 110, .ohm     = 1108 },
 114        { .temp_C       = 115, .ohm     = 949 },
 115        { .temp_C       = 120, .ohm     = 817 },
 116        { .temp_C       = 125, .ohm     = 707 },
 117};
 118
 119struct ntc_data {
 120        struct device *hwmon_dev;
 121        struct ntc_thermistor_platform_data *pdata;
 122        const struct ntc_compensation *comp;
 123        struct device *dev;
 124        int n_comp;
 125        char name[PLATFORM_NAME_SIZE];
 126};
 127
 128static inline u64 div64_u64_safe(u64 dividend, u64 divisor)
 129{
 130        if (divisor == 0 && dividend == 0)
 131                return 0;
 132        if (divisor == 0)
 133                return UINT_MAX;
 134        return div64_u64(dividend, divisor);
 135}
 136
 137static int get_ohm_of_thermistor(struct ntc_data *data, unsigned int uV)
 138{
 139        struct ntc_thermistor_platform_data *pdata = data->pdata;
 140        u64 mV = uV / 1000;
 141        u64 pmV = pdata->pullup_uV / 1000;
 142        u64 N, puO, pdO;
 143        puO = pdata->pullup_ohm;
 144        pdO = pdata->pulldown_ohm;
 145
 146        if (mV == 0) {
 147                if (pdata->connect == NTC_CONNECTED_POSITIVE)
 148                        return INT_MAX;
 149                return 0;
 150        }
 151        if (mV >= pmV)
 152                return (pdata->connect == NTC_CONNECTED_POSITIVE) ?
 153                        0 : INT_MAX;
 154
 155        if (pdata->connect == NTC_CONNECTED_POSITIVE && puO == 0)
 156                N = div64_u64_safe(pdO * (pmV - mV), mV);
 157        else if (pdata->connect == NTC_CONNECTED_GROUND && pdO == 0)
 158                N = div64_u64_safe(puO * mV, pmV - mV);
 159        else if (pdata->connect == NTC_CONNECTED_POSITIVE)
 160                N = div64_u64_safe(pdO * puO * (pmV - mV),
 161                                puO * mV - pdO * (pmV - mV));
 162        else
 163                N = div64_u64_safe(pdO * puO * mV, pdO * (pmV - mV) - puO * mV);
 164
 165        if (N > INT_MAX)
 166                N = INT_MAX;
 167        return N;
 168}
 169
 170static void lookup_comp(struct ntc_data *data, unsigned int ohm,
 171                        int *i_low, int *i_high)
 172{
 173        int start, end, mid;
 174
 175        /*
 176         * Handle special cases: Resistance is higher than or equal to
 177         * resistance in first table entry, or resistance is lower or equal
 178         * to resistance in last table entry.
 179         * In these cases, return i_low == i_high, either pointing to the
 180         * beginning or to the end of the table depending on the condition.
 181         */
 182        if (ohm >= data->comp[0].ohm) {
 183                *i_low = 0;
 184                *i_high = 0;
 185                return;
 186        }
 187        if (ohm <= data->comp[data->n_comp - 1].ohm) {
 188                *i_low = data->n_comp - 1;
 189                *i_high = data->n_comp - 1;
 190                return;
 191        }
 192
 193        /* Do a binary search on compensation table */
 194        start = 0;
 195        end = data->n_comp;
 196        while (start < end) {
 197                mid = start + (end - start) / 2;
 198                /*
 199                 * start <= mid < end
 200                 * data->comp[start].ohm > ohm >= data->comp[end].ohm
 201                 *
 202                 * We could check for "ohm == data->comp[mid].ohm" here, but
 203                 * that is a quite unlikely condition, and we would have to
 204                 * check again after updating start. Check it at the end instead
 205                 * for simplicity.
 206                 */
 207                if (ohm >= data->comp[mid].ohm) {
 208                        end = mid;
 209                } else {
 210                        start = mid + 1;
 211                        /*
 212                         * ohm >= data->comp[start].ohm might be true here,
 213                         * since we set start to mid + 1. In that case, we are
 214                         * done. We could keep going, but the condition is quite
 215                         * likely to occur, so it is worth checking for it.
 216                         */
 217                        if (ohm >= data->comp[start].ohm)
 218                                end = start;
 219                }
 220                /*
 221                 * start <= end
 222                 * data->comp[start].ohm >= ohm >= data->comp[end].ohm
 223                 */
 224        }
 225        /*
 226         * start == end
 227         * ohm >= data->comp[end].ohm
 228         */
 229        *i_low = end;
 230        if (ohm == data->comp[end].ohm)
 231                *i_high = end;
 232        else
 233                *i_high = end - 1;
 234}
 235
 236static int get_temp_mC(struct ntc_data *data, unsigned int ohm)
 237{
 238        int low, high;
 239        int temp;
 240
 241        lookup_comp(data, ohm, &low, &high);
 242        if (low == high) {
 243                /* Unable to use linear approximation */
 244                temp = data->comp[low].temp_C * 1000;
 245        } else {
 246                temp = data->comp[low].temp_C * 1000 +
 247                        ((data->comp[high].temp_C - data->comp[low].temp_C) *
 248                         1000 * ((int)ohm - (int)data->comp[low].ohm)) /
 249                        ((int)data->comp[high].ohm - (int)data->comp[low].ohm);
 250        }
 251        return temp;
 252}
 253
 254static int ntc_thermistor_get_ohm(struct ntc_data *data)
 255{
 256        int read_uV;
 257
 258        if (data->pdata->read_ohm)
 259                return data->pdata->read_ohm();
 260
 261        if (data->pdata->read_uV) {
 262                read_uV = data->pdata->read_uV();
 263                if (read_uV < 0)
 264                        return read_uV;
 265                return get_ohm_of_thermistor(data, read_uV);
 266        }
 267        return -EINVAL;
 268}
 269
 270static ssize_t ntc_show_name(struct device *dev,
 271                struct device_attribute *attr, char *buf)
 272{
 273        struct ntc_data *data = dev_get_drvdata(dev);
 274
 275        return sprintf(buf, "%s\n", data->name);
 276}
 277
 278static ssize_t ntc_show_type(struct device *dev,
 279                struct device_attribute *attr, char *buf)
 280{
 281        return sprintf(buf, "4\n");
 282}
 283
 284static ssize_t ntc_show_temp(struct device *dev,
 285                struct device_attribute *attr, char *buf)
 286{
 287        struct ntc_data *data = dev_get_drvdata(dev);
 288        int ohm;
 289
 290        ohm = ntc_thermistor_get_ohm(data);
 291        if (ohm < 0)
 292                return ohm;
 293
 294        return sprintf(buf, "%d\n", get_temp_mC(data, ohm));
 295}
 296
 297static SENSOR_DEVICE_ATTR(temp1_type, S_IRUGO, ntc_show_type, NULL, 0);
 298static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, ntc_show_temp, NULL, 0);
 299static DEVICE_ATTR(name, S_IRUGO, ntc_show_name, NULL);
 300
 301static struct attribute *ntc_attributes[] = {
 302        &dev_attr_name.attr,
 303        &sensor_dev_attr_temp1_type.dev_attr.attr,
 304        &sensor_dev_attr_temp1_input.dev_attr.attr,
 305        NULL,
 306};
 307
 308static const struct attribute_group ntc_attr_group = {
 309        .attrs = ntc_attributes,
 310};
 311
 312static int ntc_thermistor_probe(struct platform_device *pdev)
 313{
 314        struct ntc_data *data;
 315        struct ntc_thermistor_platform_data *pdata = pdev->dev.platform_data;
 316        int ret = 0;
 317
 318        if (!pdata) {
 319                dev_err(&pdev->dev, "No platform init data supplied.\n");
 320                return -ENODEV;
 321        }
 322
 323        /* Either one of the two is required. */
 324        if (!pdata->read_uV && !pdata->read_ohm) {
 325                dev_err(&pdev->dev,
 326                        "Both read_uV and read_ohm missing. Need either one of the two.\n");
 327                return -EINVAL;
 328        }
 329
 330        if (pdata->read_uV && pdata->read_ohm) {
 331                dev_warn(&pdev->dev,
 332                         "Only one of read_uV and read_ohm is needed; ignoring read_uV.\n");
 333                pdata->read_uV = NULL;
 334        }
 335
 336        if (pdata->read_uV && (pdata->pullup_uV == 0 ||
 337                                (pdata->pullup_ohm == 0 && pdata->connect ==
 338                                 NTC_CONNECTED_GROUND) ||
 339                                (pdata->pulldown_ohm == 0 && pdata->connect ==
 340                                 NTC_CONNECTED_POSITIVE) ||
 341                                (pdata->connect != NTC_CONNECTED_POSITIVE &&
 342                                 pdata->connect != NTC_CONNECTED_GROUND))) {
 343                dev_err(&pdev->dev,
 344                        "Required data to use read_uV not supplied.\n");
 345                return -EINVAL;
 346        }
 347
 348        data = devm_kzalloc(&pdev->dev, sizeof(struct ntc_data), GFP_KERNEL);
 349        if (!data)
 350                return -ENOMEM;
 351
 352        data->dev = &pdev->dev;
 353        data->pdata = pdata;
 354        strlcpy(data->name, pdev->id_entry->name, sizeof(data->name));
 355
 356        switch (pdev->id_entry->driver_data) {
 357        case TYPE_NCPXXWB473:
 358                data->comp = ncpXXwb473;
 359                data->n_comp = ARRAY_SIZE(ncpXXwb473);
 360                break;
 361        case TYPE_NCPXXWL333:
 362                data->comp = ncpXXwl333;
 363                data->n_comp = ARRAY_SIZE(ncpXXwl333);
 364                break;
 365        default:
 366                dev_err(&pdev->dev, "Unknown device type: %lu(%s)\n",
 367                                pdev->id_entry->driver_data,
 368                                pdev->id_entry->name);
 369                return -EINVAL;
 370        }
 371
 372        platform_set_drvdata(pdev, data);
 373
 374        ret = sysfs_create_group(&data->dev->kobj, &ntc_attr_group);
 375        if (ret) {
 376                dev_err(data->dev, "unable to create sysfs files\n");
 377                return ret;
 378        }
 379
 380        data->hwmon_dev = hwmon_device_register(data->dev);
 381        if (IS_ERR(data->hwmon_dev)) {
 382                dev_err(data->dev, "unable to register as hwmon device.\n");
 383                ret = PTR_ERR(data->hwmon_dev);
 384                goto err_after_sysfs;
 385        }
 386
 387        dev_info(&pdev->dev, "Thermistor %s:%d (type: %s/%lu) successfully probed.\n",
 388                        pdev->name, pdev->id, pdev->id_entry->name,
 389                        pdev->id_entry->driver_data);
 390        return 0;
 391err_after_sysfs:
 392        sysfs_remove_group(&data->dev->kobj, &ntc_attr_group);
 393        return ret;
 394}
 395
 396static int ntc_thermistor_remove(struct platform_device *pdev)
 397{
 398        struct ntc_data *data = platform_get_drvdata(pdev);
 399
 400        hwmon_device_unregister(data->hwmon_dev);
 401        sysfs_remove_group(&data->dev->kobj, &ntc_attr_group);
 402        platform_set_drvdata(pdev, NULL);
 403
 404        return 0;
 405}
 406
 407static const struct platform_device_id ntc_thermistor_id[] = {
 408        { "ncp15wb473", TYPE_NCPXXWB473 },
 409        { "ncp18wb473", TYPE_NCPXXWB473 },
 410        { "ncp21wb473", TYPE_NCPXXWB473 },
 411        { "ncp03wb473", TYPE_NCPXXWB473 },
 412        { "ncp15wl333", TYPE_NCPXXWL333 },
 413        { },
 414};
 415
 416static struct platform_driver ntc_thermistor_driver = {
 417        .driver = {
 418                .name = "ntc-thermistor",
 419                .owner = THIS_MODULE,
 420        },
 421        .probe = ntc_thermistor_probe,
 422        .remove = ntc_thermistor_remove,
 423        .id_table = ntc_thermistor_id,
 424};
 425
 426module_platform_driver(ntc_thermistor_driver);
 427
 428MODULE_DESCRIPTION("NTC Thermistor Driver");
 429MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
 430MODULE_LICENSE("GPL");
 431MODULE_ALIAS("platform:ntc-thermistor");
 432
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.