Re: [PATCH] drivers/hwmon NTC Thermistor Initial Support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Mon, Nov 29, 2010 at 01:38:04AM -0500, MyungJoo Ham wrote:
> This patch adds support for NTC Thermistor series. In this initial
> release, the followings are supported: NCP15WB473, NCP18WB473,
> NCP03WB473, and NCP15WL333. This driver is based on the datasheet of
> MURATA.
> 
> The driver in the patch includes:
> 1. Conversion from the raw ADC value (either voltage or resistence) to
> temperature. In order to use voltage value as the input, the cirsuit
> schematics should be provided with platform data.
> 2. Monitor the temperature periodically and calls notifier provided with
> the platform data.
> 
Couple of high level comments below.

> Signed-off-by: MyungJoo Ham <myungjoo.ham@xxxxxxxxxxx>
> Signed-off-by: Kyungmin Park <kyungmin.park@xxxxxxxxxxx>
> ---
>  drivers/hwmon/Kconfig  |   10 +
>  drivers/hwmon/Makefile |    1 +
>  drivers/hwmon/ntc.c    |  652 ++++++++++++++++++++++++++++++++++++++++++++++++
>  include/linux/ntc.h    |  154 ++++++++++++
>  4 files changed, 817 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/hwmon/ntc.c
>  create mode 100644 include/linux/ntc.h
> 
You should also provide Documentation/hwmon/ntc to describe driver functionality
and sysfs attributes.

> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 97499d0..639f362 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -1173,6 +1173,16 @@ config SENSORS_MC13783_ADC
>          help
>            Support for the A/D converter on MC13783 PMIC.
> 
> +config SENSORS_NTC_THERMISTOR
> +       bool "NTC thermistor support"
> +       help
> +         This driver supports NTC thermistors sensor reading and its
> +         interpretation. The driver can also monitor the temperature and
> +         send notifications about the temperature.
> +
> +         Currently, among those many NTC thermistors, this driver supports
> +         NCP15WB473, NCP18WB473, NCP21WB473, NCP03WB473, and NCP15WL333.
> +
>  if ACPI
> 
>  comment "ACPI drivers"
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index e3c2484..6cf160f 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -108,6 +108,7 @@ obj-$(CONFIG_SENSORS_W83L785TS)     += w83l785ts.o
>  obj-$(CONFIG_SENSORS_W83L786NG)        += w83l786ng.o
>  obj-$(CONFIG_SENSORS_WM831X)   += wm831x-hwmon.o
>  obj-$(CONFIG_SENSORS_WM8350)   += wm8350-hwmon.o
> +obj-$(CONFIG_SENSORS_NTC_THERMISTOR)   += ntc.o
> 
>  ifeq ($(CONFIG_HWMON_DEBUG_CHIP),y)
>  EXTRA_CFLAGS += -DDEBUG
> diff --git a/drivers/hwmon/ntc.c b/drivers/hwmon/ntc.c
> new file mode 100644
> index 0000000..2a44c83
> --- /dev/null
> +++ b/drivers/hwmon/ntc.c
> @@ -0,0 +1,652 @@
> +/*
> + * ntc.c - NTC Thermistors
> + *
> + *  Copyright (C) 2010 Samsung Electronics
> + *  MyungJoo Ham <myungjoo.ham@xxxxxxxxxxx>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
> + *
> + * In the initial version, it only supports ncp15wb473. However, adding
> + * device ids and compenstation tables provides support for more chips
> + * easily.
> + */
> +
> +#include <linux/slab.h>
> +#include <linux/module.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/math64.h>
> +#include <linux/platform_device.h>
> +#include <linux/notifier.h>
> +#include <linux/workqueue.h>
> +#include <linux/err.h>
> +
> +#include <linux/ntc.h>
> +
> +#include <linux/hwmon.h>
> +#include <linux/hwmon-sysfs.h>
> +
> +struct ntc_compensation {
> +       int             temp_C;
> +       unsigned int    ohm;
> +};
> +
> +/* Specification from Murata NTC Thermistors Datasheet */
> +struct ntc_compensation ncpXXwb473[] = {
> +       { .temp_C       = -40, .ohm     = 1747920 },
> +       { .temp_C       = -35, .ohm     = 1245428 },
> +       { .temp_C       = -30, .ohm     = 898485 },
> +       { .temp_C       = -25, .ohm     = 655802 },
> +       { .temp_C       = -20, .ohm     = 483954 },
> +       { .temp_C       = -15, .ohm     = 360850 },
> +       { .temp_C       = -10, .ohm     = 271697 },
> +       { .temp_C       = -5, .ohm      = 206463 },
> +       { .temp_C       = 0, .ohm       = 158214 },
> +       { .temp_C       = 5, .ohm       = 122259 },
> +       { .temp_C       = 10, .ohm      = 95227 },
> +       { .temp_C       = 15, .ohm      = 74730 },
> +       { .temp_C       = 20, .ohm      = 59065 },
> +       { .temp_C       = 25, .ohm      = 47000 },
> +       { .temp_C       = 30, .ohm      = 37643 },
> +       { .temp_C       = 35, .ohm      = 30334 },
> +       { .temp_C       = 40, .ohm      = 24591 },
> +       { .temp_C       = 45, .ohm      = 20048 },
> +       { .temp_C       = 50, .ohm      = 16433 },
> +       { .temp_C       = 55, .ohm      = 13539 },
> +       { .temp_C       = 60, .ohm      = 11209 },
> +       { .temp_C       = 65, .ohm      = 9328 },
> +       { .temp_C       = 70, .ohm      = 7798 },
> +       { .temp_C       = 75, .ohm      = 6544 },
> +       { .temp_C       = 80, .ohm      = 5518 },
> +       { .temp_C       = 85, .ohm      = 4674 },
> +       { .temp_C       = 90, .ohm      = 3972 },
> +       { .temp_C       = 95, .ohm      = 3388 },
> +       { .temp_C       = 100, .ohm     = 2902 },
> +       { .temp_C       = 105, .ohm     = 2494 },
> +       { .temp_C       = 110, .ohm     = 2150 },
> +       { .temp_C       = 115, .ohm     = 1860 },
> +       { .temp_C       = 120, .ohm     = 1615 },
> +       { .temp_C       = 125, .ohm     = 1406 },
> +};
> +struct ntc_compensation ncpXXwl333[] = {
> +       { .temp_C       = -40, .ohm     =1610154 },
> +       { .temp_C       = -35, .ohm     =1130850 },
> +       { .temp_C       = -30, .ohm     =802609 },
> +       { .temp_C       = -25, .ohm     =575385 },
> +       { .temp_C       = -20, .ohm     =416464 },
> +       { .temp_C       = -15, .ohm     =304219 },
> +       { .temp_C       = -10, .ohm     =224193 },
> +       { .temp_C       = -5, .ohm      =166623 },
> +       { .temp_C       = 0, .ohm       =124850 },
> +       { .temp_C       = 5, .ohm       =94287 },
> +       { .temp_C       = 10, .ohm      =71747 },
> +       { .temp_C       = 15, .ohm      =54996 },
> +       { .temp_C       = 20, .ohm      =42455 },
> +       { .temp_C       = 25, .ohm      =33000 },
> +       { .temp_C       = 30, .ohm      =25822 },
> +       { .temp_C       = 35, .ohm      =20335 },
> +       { .temp_C       = 40, .ohm      =16115 },
> +       { .temp_C       = 45, .ohm      =12849 },
> +       { .temp_C       = 50, .ohm      =10306 },
> +       { .temp_C       = 55, .ohm      =8314 },
> +       { .temp_C       = 60, .ohm      =6746 },
> +       { .temp_C       = 65, .ohm      =5503 },
> +       { .temp_C       = 70, .ohm      =4513 },
> +       { .temp_C       = 75, .ohm      =3721 },
> +       { .temp_C       = 80, .ohm      =3084 },
> +       { .temp_C       = 85, .ohm      =2569 },
> +       { .temp_C       = 90, .ohm      =2151 },
> +       { .temp_C       = 95, .ohm      =1809 },
> +       { .temp_C       = 100, .ohm     =1529 },
> +       { .temp_C       = 105, .ohm     =1299 },
> +       { .temp_C       = 110, .ohm     =1108 },
> +       { .temp_C       = 115, .ohm     =949 },
> +       { .temp_C       = 120, .ohm     =817 },
> +       { .temp_C       = 125, .ohm     =707 },
> +};
> +
Common and expected approach per hwmon sysfs ABI is to report raw values
and convert those using a configuration file. Doing so would significantly
simplify this driver, so I would suggest to use that approach.

> +struct ntc_data {
> +       struct device *hwmon_dev;
> +       struct ntc_thermistor_platform_data *pdata;
> +       struct ntc_compensation *comp;
> +       struct device *dev;
> +       int n_comp;
> +
> +       struct mutex monitor_lock;
> +       enum ntc_status status;
> +       struct delayed_work work;
> +       unsigned long interval_jiffies;
> +};
> +
> +static unsigned int get_ohm_of_thermistor(struct ntc_data *data,
> +               unsigned int uV)
> +{
> +       struct ntc_thermistor_platform_data *pdata = data->pdata;
> +       u64 mV = uV / 1000;
> +       u64 pmV = pdata->pullup_uV / 1000;
> +       u64 N, puO, pdO;
> +       puO = pdata->pullup_ohm;
> +       pdO = pdata->pulldown_ohm;
> +
> +       if (mV >= pmV)
> +               return (pdata->connect == NTC_CONNECTED_POSITIVE) ?
> +                       0 : UINT_MAX;
> +
> +       if (pdata->connect == NTC_CONNECTED_POSITIVE &&
> +                       puO == 0)
> +               N = div64_u64(pdO * (pmV - mV), mV);
> +       else if (pdata->connect == NTC_CONNECTED_GROUND &&
> +                       pdO == 0)
> +               N = div64_u64(puO * mV, pmV - mV);
> +       else if (pdata->connect == NTC_CONNECTED_POSITIVE)
> +               N = div64_u64(pdO * puO * (pmV - mV),
> +                               puO * mV -
> +                               pdO * (pmV - mV));
> +       else
> +               N = div64_u64(pdO * puO * mV,
> +                               pdO * (pmV - mV) -
> +                               puO * mV);
> +
> +       return (u32) N;
> +}
> +
> +static int lookup_comp(struct ntc_data *data,
> +               unsigned int ohm, int *i_low, int *i_high)
> +{
> +       /*  greatest lower bound and least upper bound */
> +       int glb = -1, lub = -1;
> +       unsigned int glb_ohm = 0, lub_ohm = 0;
> +       int i;
> +       for (i = 0; i < data->n_comp; i++) {
> +               if (data->comp[i].ohm <= ohm &&
> +                               (glb == -1 || glb_ohm < data->comp[i].ohm)) {
> +                       glb = i;
> +                       glb_ohm = data->comp[i].ohm;
> +               }
> +
> +               if (data->comp[i].ohm >= ohm &&
> +                               (lub == -1 || lub_ohm > data->comp[i].ohm)) {
> +                       lub = i;
> +                       lub_ohm = data->comp[i].ohm;
> +               }
> +       }
> +       *i_low = glb;
> +       *i_high = lub;
> +
> +       if (glb == -1 || lub == -1)
> +               return -EINVAL;
> +       return 0;
> +}
> +
> +static int get_temp_mC(struct ntc_data *data,
> +               unsigned int ohm)
> +{
> +       int low, high;
> +       int ret;
> +
> +       ret = lookup_comp(data, ohm, &low, &high);
> +       if (ret) {
> +               /* Unable to use linear approximation */
> +               if (low != -1)
> +                       return data->comp[low].temp_C * 1000;
> +               if (high != -1)
> +                       return data->comp[high].temp_C * 1000;
> +
> +               pr_err("ntc thermistor: data not valid: %dohm.\n", ohm);
> +               return 25 * 1000; /* Assume 25C */
> +       }
> +
> +       return data->comp[low].temp_C * 1000 +
> +               ((data->comp[high].temp_C - data->comp[low].temp_C ) * 1000 *
> +               ((int)ohm - (int)data->comp[low].ohm)) /
> +               ((int)data->comp[high].ohm - (int)data->comp[low].ohm);
> +}
> +
> +static int _ntc_thermistor_read(struct ntc_data *data);
> +static int ntc_monitor_measure(struct ntc_data *data)
> +{
> +       struct ntc_temperature_monitor *mon = &data->pdata->monitor;
> +       int measure, min = INT_MAX, max = INT_MIN;
> +       int i;
> +
> +       measure = _ntc_thermistor_read(data);
> +       for (i = 1; i < mon->number_of_measure; i++) {
> +               int m = _ntc_thermistor_read(data);
> +               measure += m;
> +               if (m < min)
> +                       min = m;
> +               if (m > max)
> +                       max = m;
> +       }
> +
> +       if (mon->number_of_measure >= 3 && mon->discard_extreme)
> +               return (measure - min - max) / (mon->number_of_measure - 2) / 1000;
> +
> +       return measure / ((mon->number_of_measure > 1) ?
> +                       mon->number_of_measure : 1) / 1000;
> +}
> +
> +static void ntc_monitor(struct work_struct *work)
> +{
> +       struct delayed_work *dwork =
> +               container_of(work, struct delayed_work, work);
> +       struct ntc_data *data = container_of(dwork, struct ntc_data, work);
> +       struct ntc_temperature_monitor *mon = &data->pdata->monitor;
> +
> +       dev_dbg(data->dev, "NTC Monitor Event..\n");
> +
> +       /* Worth measure it? */
> +       if (mon->notifier &&
> +                       (mon->too_hot < INT_MAX || mon->too_cold > INT_MIN)) {
> +               mon->last_measure = ntc_monitor_measure(data);
> +               dev_info(data->dev, "Temperature: %dC\n", mon->last_measure);
> +
> +               if (mon->last_measure >= mon->too_hot) {
> +                       srcu_notifier_call_chain(mon->notifier, 1, mon);
> +                       data->status = NTC_TOO_HOT;
> +               } else if (mon->last_measure <= mon->too_cold) {
> +                       srcu_notifier_call_chain(mon->notifier, -1, mon);
> +                       data->status = NTC_TOO_COLD;
> +               } else if (mon->last_measure >= mon->hot &&
> +                               data->status == NTC_TOO_HOT) {
> +                       srcu_notifier_call_chain(mon->notifier, 1, mon);
> +                       data->status = NTC_TOO_HOT;
> +               } else if (mon->last_measure <= mon->cold &&
> +                               data->status == NTC_TOO_COLD) {
> +                       srcu_notifier_call_chain(mon->notifier, -1, mon);
> +                       data->status = NTC_TOO_COLD;
> +               } else if (data->status == NTC_TOO_COLD ||
> +                               data->status == NTC_TOO_HOT) {
> +                       srcu_notifier_call_chain(mon->notifier, 0, mon);
> +                       data->status = NTC_NORMAL;
> +               } else
> +                       data->status = NTC_NORMAL;
> +       }
> +
> +       /* Appoint the next monitor execution */
> +       mutex_lock(&data->monitor_lock);
> +
> +       if (data->interval_jiffies) {
> +               cancel_delayed_work(&data->work);
> +               schedule_delayed_work(&data->work, data->interval_jiffies);
> +       }
> +
> +       mutex_unlock(&data->monitor_lock);
> +}
> +
> +static ssize_t ntc_show_status(struct device *dev,
> +                               struct device_attribute *attr,
> +                               char *buf)
> +{
> +       struct platform_device *pdev = container_of
> +               (dev, struct platform_device, dev);
> +       struct ntc_data *data = platform_get_drvdata(pdev);
> +       switch (data->status) {
> +       case NTC_NORMAL:
> +               return sprintf(buf, "normal\n");
> +               break;
> +       case NTC_TOO_HOT:
> +               return sprintf(buf, "too hot\n");
> +               break;
> +       case NTC_TOO_COLD:
> +               return sprintf(buf, "too cold\n");
> +               break;
> +       }
> +       return -EINVAL;
> +}
> +
> +static ssize_t ntc_show_temp(struct device *dev,
> +                               struct device_attribute *attr,
> +                               char *buf)
> +{
> +       struct platform_device *pdev = container_of
> +               (dev, struct platform_device, dev);
> +       struct ntc_data *data = platform_get_drvdata(pdev);
> +       int temp = ntc_monitor_measure(data);
> +       return sprintf(buf, "%d C\n", temp);
> +}
> +
> +static ssize_t ntc_show_interval(struct device *dev,
> +                               struct device_attribute *attr,
> +                               char *buf)
> +{
> +       struct platform_device *pdev = container_of
> +               (dev, struct platform_device, dev);
> +       struct ntc_data *data = platform_get_drvdata(pdev);
> +       return sprintf(buf, "%d ms\n", data->pdata->monitor.interval_ms);
> +}
> +
> +static ssize_t ntc_set_interval(struct device *dev,
> +                              struct device_attribute *attr,
> +                              const char *buf, size_t count)
> +{
> +       struct platform_device *pdev = container_of
> +               (dev, struct platform_device, dev);
> +       struct ntc_data *data = platform_get_drvdata(pdev);
> +       unsigned long val;
> +       unsigned long jiffies;
> +
> +       if (strict_strtoul(buf, 10, &val))
> +               return -EINVAL;
> +
> +       jiffies = msecs_to_jiffies(val);
> +
> +       mutex_lock(&data->monitor_lock);
> +
> +       data->interval_jiffies = jiffies;
> +       cancel_delayed_work(&data->work);
> +       if (jiffies > 0)
> +               schedule_delayed_work(&data->work, jiffies);
> +
> +       mutex_unlock(&data->monitor_lock);
> +
> +       return count;
> +}
> +
> +static ssize_t ntc_show_mon_cond(struct device *dev,
> +                               struct device_attribute *attr,
> +                               char *buf)
> +{
> +       int index = to_sensor_dev_attr(attr)->index;
> +       struct platform_device *pdev = container_of
> +               (dev, struct platform_device, dev);
> +       struct ntc_data *data = platform_get_drvdata(pdev);
> +       int show = INT_MIN;
> +       switch (index) {
> +       case 0:
> +               show = data->pdata->monitor.too_hot;
> +               break;
> +       case 1:
> +               show = data->pdata->monitor.too_cold;
> +               break;
> +       case 2:
> +               show = data->pdata->monitor.hot;
> +               break;
> +       case 3:
> +               show = data->pdata->monitor.cold;
> +               break;
> +       default:
> +               return -EINVAL;
> +       }
> +
> +       return sprintf(buf, "%d\n", show);
> +}
> +
> +static ssize_t ntc_set_mon_cond(struct device *dev,
> +                              struct device_attribute *attr,
> +                              const char *buf, size_t count)
> +{
> +       int index = to_sensor_dev_attr(attr)->index;
> +       struct platform_device *pdev = container_of
> +               (dev, struct platform_device, dev);
> +       struct ntc_data *data = platform_get_drvdata(pdev);
> +       long val;
> +
> +       if (strict_strtol(buf, 10, &val))
> +               return -EINVAL;
> +
> +       switch (index) {
> +       case 0:
> +               data->pdata->monitor.too_hot = val;
> +               break;
> +       case 1:
> +               data->pdata->monitor.too_cold = val;
> +               break;
> +       case 2:
> +               data->pdata->monitor.hot = val;
> +               break;
> +       case 3:
> +               data->pdata->monitor.cold = val;
> +               break;
> +       default:
> +               return -EINVAL;
> +       }
> +
> +       return count;
> +}
> +
> +static SENSOR_DEVICE_ATTR(status, S_IRUGO, ntc_show_status, NULL, 0);
> +static SENSOR_DEVICE_ATTR(temperature, S_IRUGO, ntc_show_temp, NULL, 0);
> +static SENSOR_DEVICE_ATTR(measure_interval, S_IWUSR | S_IRUGO,
> +               ntc_show_interval, ntc_set_interval, 0);
> +static SENSOR_DEVICE_ATTR(too_hot, S_IWUSR | S_IRUGO, ntc_show_mon_cond,
> +               ntc_set_mon_cond, 0);
> +static SENSOR_DEVICE_ATTR(too_cold, S_IWUSR | S_IRUGO, ntc_show_mon_cond,
> +               ntc_set_mon_cond, 1);
> +static SENSOR_DEVICE_ATTR(hot, S_IWUSR | S_IRUGO, ntc_show_mon_cond,
> +               ntc_set_mon_cond, 2);
> +static SENSOR_DEVICE_ATTR(cold, S_IWUSR | S_IRUGO, ntc_show_mon_cond,
> +               ntc_set_mon_cond, 3);

The above attributes don't use the hwmon ABI. Please have a look into
Documentation/hwmon/sysfs-interface and use the attribute names provided there.

> +static struct attribute *ntc_attributes[] = {
> +       &sensor_dev_attr_status.dev_attr.attr,
> +       &sensor_dev_attr_temperature.dev_attr.attr,
> +       &sensor_dev_attr_measure_interval.dev_attr.attr,
> +       &sensor_dev_attr_too_hot.dev_attr.attr,
> +       &sensor_dev_attr_too_cold.dev_attr.attr,
> +       &sensor_dev_attr_hot.dev_attr.attr,
> +       &sensor_dev_attr_cold.dev_attr.attr,
> +       NULL,
> +};
> +static const struct attribute_group ntc_attr_group = {
> +       .attrs = ntc_attributes,
> +};
> +
> +static int __devinit ntc_thermistor_probe(struct platform_device *pdev)
> +{
> +       struct ntc_data *data;
> +       struct ntc_thermistor_platform_data *pdata =
> +               pdev->dev.platform_data;
> +       int ret = 0;
> +
> +       data = kzalloc(sizeof(struct ntc_data), GFP_KERNEL);
> +       if (!data)
> +               return -ENOMEM;
> +
> +       if (!pdata) {
> +               dev_err(&pdev->dev, "No platform init data supplied.\n");
> +               ret = -ENODEV;
> +               goto err;
> +       }
> +
> +       data->dev = &pdev->dev;
> +       data->pdata = pdata;
> +       data->status = NTC_NORMAL;
> +       mutex_init(&data->monitor_lock);
> +
> +       ret = sysfs_create_group(&data->dev->kobj, &ntc_attr_group);
> +       if (ret) {
> +               dev_err(data->dev, "unable to create sysfs files\n");
> +               goto err;
> +       }
> +
> +       data->hwmon_dev = hwmon_device_register(data->dev);
> +       if (IS_ERR_OR_NULL(data->hwmon_dev)) {
> +               dev_err(data->dev, "unable to register as hwmon device.\n");
> +               ret = -EINVAL;
> +               goto err_after_sysfs;
> +       }
> +
> +       /* Either one of the two is required. */
> +       if (!pdata->read_uV && !pdata->read_ohm) {
> +               dev_err(&pdev->dev, "Both read_uV and read_ohm missing."
> +                               "Need either one of the two.\n");
> +               ret = -EINVAL;
> +               goto err_after_sysfs;
> +       }
> +
> +       if (pdata->read_uV && pdata->read_ohm) {
> +               dev_warn(&pdev->dev, "Only one of read_uV and read_ohm "
> +                               "is needed; ignoring read_uV.\n");
> +               pdata->read_uV = NULL;
> +       }
> +
> +       if (pdata->read_uV && (pdata->pullup_uV == 0 ||
> +                               (pdata->pullup_ohm == 0 && pdata->connect == NTC_CONNECTED_GROUND) ||
> +                               (pdata->pulldown_ohm == 0 && pdata->connect == NTC_CONNECTED_POSITIVE) ||
> +                               (pdata->connect != NTC_CONNECTED_POSITIVE &&
> +                                pdata->connect != NTC_CONNECTED_GROUND))) {
> +               dev_err(&pdev->dev, "Required data to use read_uV not "
> +                               "supplied.\n");
> +               ret = -EINVAL;
> +               goto err_after_sysfs;
> +       }
> +
> +       switch (pdev->id_entry->driver_data) {
> +       case TYPE_NCPXXWB473:
> +               data->comp = ncpXXwb473;
> +               data->n_comp = ARRAY_SIZE(ncpXXwb473);
> +               break;
> +       case TYPE_NCPXXWL333:
> +               data->comp = ncpXXwl333;
> +               data->n_comp = ARRAY_SIZE(ncpXXwl333);
> +               break;
> +       default:
> +               dev_err(&pdev->dev, "Unknown device type: %lu(%s)\n",
> +                               pdev->id_entry->driver_data,
> +                               pdev->id_entry->name);
> +               ret = -EINVAL;
> +               goto err_after_sysfs;
> +       }
> +
> +       platform_set_drvdata(pdev, data);
> +
> +       INIT_DELAYED_WORK(&data->work, ntc_monitor);
> +
> +       mutex_lock(&data->monitor_lock);
> +
> +       if (pdata->monitor.interval_ms) {
> +               data->interval_jiffies =
> +                       msecs_to_jiffies(pdata->monitor.interval_ms);
> +
> +               if (data->interval_jiffies > 0)
> +                       schedule_delayed_work(&data->work, jiffies);
> +               else
> +                       dev_warn(&pdev->dev, "interval_ms %u is too small and regarded as 0.\n",
> +                                       pdata->monitor.interval_ms);
> +       }
> +
> +       mutex_unlock(&data->monitor_lock);
> +
> +       dev_info(&pdev->dev, "Thermistor %s:%d (type: %s/%lu) successfully probed.\n",
> +                       pdev->name, pdev->id,
> +                       pdev->id_entry->name,
> +                       pdev->id_entry->driver_data);
> +       return 0;
> +err_after_sysfs:
> +       sysfs_remove_group(&data->dev->kobj, &ntc_attr_group);
> +err:
> +       kfree(data);
> +       return ret;
> +}
> +
> +static int __devexit ntc_thermistor_remove(struct platform_device *pdev)
> +{
> +       struct ntc_data *data = platform_get_drvdata(pdev);
> +       kfree(data);
> +       return 0;
> +}
> +
> +static const struct platform_device_id ntc_thermistor_id[] = {
> +       { "ncp15wb473", TYPE_NCPXXWB473 },
> +       { "ncp18wb473", TYPE_NCPXXWB473 },
> +       { "ncp21wb473", TYPE_NCPXXWB473 },
> +       { "ncp03wb473", TYPE_NCPXXWB473 },
> +       { "ncp15wl333", TYPE_NCPXXWL333 },
> +       { },
> +};
> +
> +static struct platform_driver ntc_thermistor_driver = {
> +       .driver = {
> +               .name = "ntc-thermistor",
> +               .owner = THIS_MODULE,
> +       },
> +       .probe = ntc_thermistor_probe,
> +       .remove = __devexit_p(ntc_thermistor_remove),
> +       .id_table = ntc_thermistor_id,
> +};
> +
> +static int __init ntc_thermistor_init(void)
> +{
> +       return platform_driver_register(&ntc_thermistor_driver);
> +}
> +device_initcall(ntc_thermistor_init);
> +
> +static void __exit ntc_thermistor_cleanup(void)
> +{
> +       platform_driver_unregister(&ntc_thermistor_driver);
> +}
> +module_exit(ntc_thermistor_cleanup);
> +
> +static int _ntc_thermistor_read(struct ntc_data *data)
> +{
> +       if (data->pdata->read_ohm)
> +               return get_temp_mC(data, data->pdata->read_ohm());
> +
> +       if (data->pdata->read_uV)
> +               return get_temp_mC(data,
> +                               get_ohm_of_thermistor(data,
> +                                       data->pdata->read_uV()));
> +
> +       dev_err(data->dev, "Sensor reading function not available.\n");
> +       return 25*1000;
> +}
> +
> +int ntc_thermistor_read(struct device *dev)
> +{
> +       return _ntc_thermistor_read(dev_get_drvdata(dev));
> +}
> +
> +void ntc_thermistor_monitor_set_too_hot(struct device *dev, int too_hot)
> +{
> +       struct ntc_data *data = dev_get_drvdata(dev);
> +
> +       mutex_lock(&data->monitor_lock);
> +       data->pdata->monitor.too_hot = too_hot;
> +       mutex_unlock(&data->monitor_lock);
> +}
> +
> +void ntc_thermistor_monitor_set_too_cold(struct device *dev, int too_cold)
> +{
> +       struct ntc_data *data = dev_get_drvdata(dev);
> +
> +       mutex_lock(&data->monitor_lock);
> +       data->pdata->monitor.too_cold = too_cold;
> +       mutex_unlock(&data->monitor_lock);
> +}
> +
> +int ntc_thermistor_monitor_start(struct device *dev, unsigned int interval_ms)
> +{
> +       struct ntc_data *data = dev_get_drvdata(dev);
> +       unsigned long jiffies = msecs_to_jiffies(interval_ms);
> +
> +       mutex_lock(&data->monitor_lock);
> +       data->pdata->monitor.interval_ms = interval_ms;
> +       data->interval_jiffies = jiffies;
> +
> +       if (jiffies > 0) {
> +               cancel_delayed_work(&data->work);
> +               schedule_delayed_work(&data->work, jiffies);
> +       }
> +
> +       mutex_unlock(&data->monitor_lock);
> +
> +       if (interval_ms > 0 && jiffies == 0)
> +               return -EINVAL;
> +       return 0;
> +}
> +
> +MODULE_DESCRIPTION("NTC Thermistor Driver");
> +MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@xxxxxxxxxxx>");
> +MODULE_LICENSE("GPL");
> +
> diff --git a/include/linux/ntc.h b/include/linux/ntc.h
> new file mode 100644
> index 0000000..4515466
> --- /dev/null
> +++ b/include/linux/ntc.h
> @@ -0,0 +1,154 @@
> +/*
> + * ntc.h - NTC Thermistors
> + *
> + *  Copyright (C) 2010 Samsung Electronics
> + *  MyungJoo Ham <myungjoo.ham@xxxxxxxxxxx>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
> + */
> +#ifndef _LINUX_NTC_H
> +#define _LINUX_NTC_H
> +
> +enum ntc_thermistor_type {
> +       TYPE_NCPXXWB473,
> +       TYPE_NCPXXWL333,
> +};
> +
> +enum ntc_status {
> +       NTC_NORMAL = 0,
> +       NTC_TOO_HOT,
> +       NTC_TOO_COLD,
> +};
> +
> +struct ntc_temperature_monitor {
> +       unsigned int interval_ms; /* 0 to disable */
> +
> +       int too_hot;    /* INT_MAX to disable */
> +       int hot;        /* denotes "recover" temp from too_hot */
> +       int too_cold;   /* INT_MIN to disable */
> +       int cold;       /* denotes "recover" temp from too_cold */
> +
> +       /* Monitor based on the average of multiple measurement */
> +       int number_of_measure;
> +       /* if number_of_measure >= 3, we may discard max and min */
> +       bool discard_extreme;
> +
> +       int last_measure; /* The recently measure temperature */
> +
> +       struct srcu_notifier_head *notifier;
> +       /*
> +        * Invoked if at least one of the following conditions meets:
> +        *      1. temp >= too_hot
> +        *      2. temp <= too_cold
> +        *      3. temp >= hot and was temp >= too_hot and never have
> +        *              recovered to temp < hot since then
> +        *      4. temp <= cold and was temp <= too_cold and never have
> +        *              recovered to temp > cold since then
> +        *      5. temp recovered to normal from an exception
> +        *              (from 1 or 3 to temp > too_cold && temp < hot
> +        *              OR
> +        *               from 2 or 4 to temp < too_hot && temp > cold)
> +        */

This approach is very unusual. I would understand support of alarm files 
(per hwmon sysfs API) and sending a poll event to those if alarm conditions are met;
see gpio-fan.c for an example how to do this. This would enable user level support,
as with other hwmon devices, and avoid driver specific code. Plus, user level code
would be much better suited to handle the cold/hot conditions than kernel code.

> +};
> +
> +struct ntc_thermistor_platform_data {
> +       /* To use read_uV, set pullup_uV, pullup_ohm, pulldown_ohm,
> +        * and connect, but you don't need read_ohm.
> +        * To use read_ohm, you don't need read_ohm and all of those.
> +        **/
> +       unsigned int (*read_uV)(void);
> +       unsigned int pullup_uV;
> +
> +       /* pullup/down_ohm: 0 for infinite / not-connected */
> +       unsigned int pullup_ohm;
> +       unsigned int pulldown_ohm;
> +       enum {NTC_CONNECTED_POSITIVE, NTC_CONNECTED_GROUND} connect;
> +
> +       /*
> +        * How to setup pullup_ohm, pulldown_ohm, connect
> +        * Note: $: resister, [TH]: thermistor
> +        *
> +        * 1. connect = NTC_CONNECTED_POSITIVE, pullup_ohm > 0
> +        *
> +        *   [pullup_uV]
> +        *       |    |
> +        *      [TH]  $ (pullup_ohm)
> +        *       |    |
> +        *       +----+-----------------------[read_uV]
> +        *       |
> +        *       $ (pulldown_ohm)
> +        *       |
> +        *      --- (ground)
> +        *
> +        * 2. connect = NTC_CONNECTED_POSITIVE, pullup_ohm = 0
> +        *
> +        *   [pullup_uV]
> +        *       |
> +        *      [TH]
> +        *       |
> +        *       +----------------------------[read_uV]
> +        *       |
> +        *       $ (pulldown_ohm)
> +        *       |
> +        *      --- (ground)
> +        *
> +        * 3. connect = NTC_CONNECTED_GROUND, pulldown_ohm > 0
> +        *
> +        *   [pullup_uV]
> +        *       |
> +        *       $ (pullup_ohm)
> +        *       |
> +        *       +----+-----------------------[read_uV]
> +        *       |    |
> +        *      [TH]  $ (pulldown_ohm)
> +        *       |    |
> +        *      -------- (ground)
> +        *
> +        * 4. connect = NTC_CONNECTED_GROUND, pulldown_ohm = 0
> +        *
> +        *   [pullup_uV]
> +        *       |
> +        *       $ (pullup_ohm)
> +        *       |
> +        *       +----------------------------[read_uV]
> +        *       |
> +        *      [TH]
> +        *       |
> +        *      --- (ground)
> +        */
> +
> +       unsigned int (*read_ohm)(void);
> +
> +       struct ntc_temperature_monitor monitor;
> +};
> +
> +extern int ntc_thermistor_read(struct device *dev);
> +
> +extern void ntc_thermistor_monitor_set_too_hot(
> +               struct device *dev, int too_hot);
> +#define ntc_thermistor_monitor_unset_too_hot(dev)      \
> +       ntc_thermistor_monitor_set_too_hot(dev, INT_MAX)
> +
> +extern void ntc_thermistor_monitor_set_too_cold(
> +               struct device *dev, int too_cold);
> +#define ntc_thermistor_monitor_unset_too_cold(dev)     \
> +       ntc_thermistor_monitor_set_too_cold(dev, INT_MIN)
> +
> +extern int ntc_thermistor_monitor_start(
> +               struct device *dev, unsigned int interval_ms);
> +#define ntc_thermistor_monitor_stop(dev)       \
> +       ntc_thermistor_monitor_start(dev, 0)
> +
> +#endif /* _LINUX_NTC_H */
> --
> 1.7.0.4
> 

_______________________________________________
lm-sensors mailing list
lm-sensors@xxxxxxxxxxxxxx
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors


[Index of Archives]     [Linux Kernel]     [Linux Hardware Monitoring]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]

  Powered by Linux