root/drivers/hwmon/tmp464.c
// SPDX-License-Identifier: GPL-2.0-or-later

/* Driver for the Texas Instruments TMP464 SMBus temperature sensor IC.
 * Supported models: TMP464, TMP468

 * Copyright (C) 2022 Agathe Porte <agathe.porte@nokia.com>
 * Preliminary support by:
 * Lionel Pouliquen <lionel.lp.pouliquen@nokia.com>
 */

#include <linux/err.h>
#include <linux/hwmon.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/regmap.h>
#include <linux/slab.h>

/* Addresses to scan */
static const unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4b, I2C_CLIENT_END };

#define TMP464_NUM_CHANNELS             5       /* chan 0 is internal, 1-4 are remote */
#define TMP468_NUM_CHANNELS             9       /* chan 0 is internal, 1-8 are remote */

#define MAX_CHANNELS                    9

#define TMP464_TEMP_REG(channel)        (channel)
#define TMP464_TEMP_OFFSET_REG(channel) (0x40 + ((channel) - 1) * 8)
#define TMP464_N_FACTOR_REG(channel)    (0x41 + ((channel) - 1) * 8)

static const u8 TMP464_THERM_LIMIT[MAX_CHANNELS] = {
        0x39, 0x42, 0x4A, 0x52, 0x5A, 0x62, 0x6a, 0x72, 0x7a };
static const u8 TMP464_THERM2_LIMIT[MAX_CHANNELS] = {
        0x3A, 0x43, 0x4B, 0x53, 0x5B, 0x63, 0x6b, 0x73, 0x7b };

#define TMP464_THERM_STATUS_REG                 0x21
#define TMP464_THERM2_STATUS_REG                0x22
#define TMP464_REMOTE_OPEN_REG                  0x23
#define TMP464_CONFIG_REG                       0x30
#define TMP464_TEMP_HYST_REG                    0x38
#define TMP464_LOCK_REG                         0xc4

/* Identification */
#define TMP464_MANUFACTURER_ID_REG              0xFE
#define TMP464_DEVICE_ID_REG                    0xFF

/* Flags */
#define TMP464_CONFIG_SHUTDOWN                  BIT(5)
#define TMP464_CONFIG_RANGE                     0x04
#define TMP464_CONFIG_REG_REN(x)                (BIT(7 + (x)))
#define TMP464_CONFIG_REG_REN_MASK              GENMASK(15, 7)
#define TMP464_CONFIG_CONVERSION_RATE_B0        2
#define TMP464_CONFIG_CONVERSION_RATE_B2        4
#define TMP464_CONFIG_CONVERSION_RATE_MASK      GENMASK(TMP464_CONFIG_CONVERSION_RATE_B2, \
                                                        TMP464_CONFIG_CONVERSION_RATE_B0)

#define TMP464_UNLOCK_VAL                       0xeb19
#define TMP464_LOCK_VAL                         0x5ca6
#define TMP464_LOCKED                           0x8000

/* Manufacturer / Device ID's */
#define TMP464_MANUFACTURER_ID                  0x5449
#define TMP464_DEVICE_ID                        0x1468
#define TMP468_DEVICE_ID                        0x0468

static const struct i2c_device_id tmp464_id[] = {
        { "tmp464", TMP464_NUM_CHANNELS },
        { "tmp468", TMP468_NUM_CHANNELS },
        { }
};
MODULE_DEVICE_TABLE(i2c, tmp464_id);

static const struct of_device_id __maybe_unused tmp464_of_match[] = {
        {
                .compatible = "ti,tmp464",
                .data = (void *)TMP464_NUM_CHANNELS
        },
        {
                .compatible = "ti,tmp468",
                .data = (void *)TMP468_NUM_CHANNELS
        },
        {},
};
MODULE_DEVICE_TABLE(of, tmp464_of_match);

struct tmp464_channel {
        const char *label;
        bool enabled;
};

struct tmp464_data {
        struct regmap *regmap;
        int channels;
        s16 config_orig;
        u16 open_reg;
        unsigned long last_updated;
        bool valid;
        int update_interval;
        struct tmp464_channel channel[MAX_CHANNELS];
};

static int temp_from_reg(s16 reg)
{
        return DIV_ROUND_CLOSEST((reg >> 3) * 625, 10);
}

static s16 temp_to_limit_reg(long temp)
{
        return DIV_ROUND_CLOSEST(temp, 500) << 6;
}

static s16 temp_to_offset_reg(long temp)
{
        return DIV_ROUND_CLOSEST(temp * 10, 625) << 3;
}

static int tmp464_enable_channels(struct tmp464_data *data)
{
        struct regmap *regmap = data->regmap;
        u16 enable = 0;
        int i;

        for (i = 0; i < data->channels; i++)
                if (data->channel[i].enabled)
                        enable |= TMP464_CONFIG_REG_REN(i);

        return regmap_update_bits(regmap, TMP464_CONFIG_REG, TMP464_CONFIG_REG_REN_MASK, enable);
}

static int tmp464_chip_read(struct device *dev, u32 attr, int channel, long *val)
{
        struct tmp464_data *data = dev_get_drvdata(dev);

        switch (attr) {
        case hwmon_chip_update_interval:
                *val = data->update_interval;
                return 0;
        default:
                return -EOPNOTSUPP;
        }
}

static int tmp464_temp_read(struct device *dev, u32 attr, int channel, long *val)
{
        struct tmp464_data *data = dev_get_drvdata(dev);
        struct regmap *regmap = data->regmap;
        unsigned int regs[2];
        unsigned int regval;
        u16 regvals[2];
        int err = 0;

        switch (attr) {
        case hwmon_temp_max_alarm:
                err = regmap_read(regmap, TMP464_THERM_STATUS_REG, &regval);
                if (err < 0)
                        break;
                *val = !!(regval & BIT(channel + 7));
                break;
        case hwmon_temp_crit_alarm:
                err = regmap_read(regmap, TMP464_THERM2_STATUS_REG, &regval);
                if (err < 0)
                        break;
                *val = !!(regval & BIT(channel + 7));
                break;
        case hwmon_temp_fault:
                /*
                 * The chip clears TMP464_REMOTE_OPEN_REG after it is read
                 * and only updates it after the next measurement cycle is
                 * complete. That means we have to cache the value internally
                 * for one measurement cycle and report the cached value.
                 */
                if (!data->valid || time_after(jiffies, data->last_updated +
                                               msecs_to_jiffies(data->update_interval))) {
                        err = regmap_read(regmap, TMP464_REMOTE_OPEN_REG, &regval);
                        if (err < 0)
                                break;
                        data->open_reg = regval;
                        data->last_updated = jiffies;
                        data->valid = true;
                }
                *val = !!(data->open_reg & BIT(channel + 7));
                break;
        case hwmon_temp_max_hyst:
                regs[0] = TMP464_THERM_LIMIT[channel];
                regs[1] = TMP464_TEMP_HYST_REG;
                err = regmap_multi_reg_read(regmap, regs, regvals, 2);
                if (err < 0)
                        break;
                *val = temp_from_reg(regvals[0] - regvals[1]);
                break;
        case hwmon_temp_max:
                err = regmap_read(regmap, TMP464_THERM_LIMIT[channel], &regval);
                if (err < 0)
                        break;
                *val = temp_from_reg(regval);
                break;
        case hwmon_temp_crit_hyst:
                regs[0] = TMP464_THERM2_LIMIT[channel];
                regs[1] = TMP464_TEMP_HYST_REG;
                err = regmap_multi_reg_read(regmap, regs, regvals, 2);
                if (err < 0)
                        break;
                *val = temp_from_reg(regvals[0] - regvals[1]);
                break;
        case hwmon_temp_crit:
                err = regmap_read(regmap, TMP464_THERM2_LIMIT[channel], &regval);
                if (err < 0)
                        break;
                *val = temp_from_reg(regval);
                break;
        case hwmon_temp_offset:
                err = regmap_read(regmap, TMP464_TEMP_OFFSET_REG(channel), &regval);
                if (err < 0)
                        break;
                *val = temp_from_reg(regval);
                break;
        case hwmon_temp_input:
                if (!data->channel[channel].enabled) {
                        err = -ENODATA;
                        break;
                }
                err = regmap_read(regmap, TMP464_TEMP_REG(channel), &regval);
                if (err < 0)
                        break;
                *val = temp_from_reg(regval);
                break;
        case hwmon_temp_enable:
                *val = data->channel[channel].enabled;
                break;
        default:
                err = -EOPNOTSUPP;
                break;
        }

        return err;
}

static int tmp464_read(struct device *dev, enum hwmon_sensor_types type,
                       u32 attr, int channel, long *val)
{
        switch (type) {
        case hwmon_chip:
                return tmp464_chip_read(dev, attr, channel, val);
        case hwmon_temp:
                return tmp464_temp_read(dev, attr, channel, val);
        default:
                return -EOPNOTSUPP;
        }
}

static int tmp464_read_string(struct device *dev, enum hwmon_sensor_types type,
                              u32 attr, int channel, const char **str)
{
        struct tmp464_data *data = dev_get_drvdata(dev);

        *str = data->channel[channel].label;

        return 0;
}

static int tmp464_set_convrate(struct tmp464_data *data, long interval)
{
        int rate;

        /*
         * For valid rates, interval in milli-seconds can be calculated as
         *      interval = 125 << (7 - rate);
         * or
         *      interval = (1 << (7 - rate)) * 125;
         * The rate is therefore
         *      rate = 7 - __fls(interval / 125);
         * and the rounded rate is
         *      rate = 7 - __fls(interval * 4 / (125 * 3));
         * Use clamp_val() to avoid overflows, and to ensure valid input
         * for __fls.
         */
        interval = clamp_val(interval, 125, 16000);
        rate = 7 - __fls(interval * 4 / (125 * 3));
        data->update_interval = 125 << (7 - rate);

        return regmap_update_bits(data->regmap, TMP464_CONFIG_REG,
                                  TMP464_CONFIG_CONVERSION_RATE_MASK,
                                  rate << TMP464_CONFIG_CONVERSION_RATE_B0);
}

static int tmp464_chip_write(struct tmp464_data *data, u32 attr, int channel, long val)
{
        switch (attr) {
        case hwmon_chip_update_interval:
                return tmp464_set_convrate(data, val);
        default:
                return -EOPNOTSUPP;
        }
}

static int tmp464_temp_write(struct tmp464_data *data, u32 attr, int channel, long val)
{
        struct regmap *regmap = data->regmap;
        unsigned int regval;
        int err = 0;

        switch (attr) {
        case hwmon_temp_max_hyst:
                err = regmap_read(regmap, TMP464_THERM_LIMIT[0], &regval);
                if (err < 0)
                        break;
                val = clamp_val(val, -256000, 256000);  /* prevent overflow/underflow */
                val = clamp_val(temp_from_reg(regval) - val, 0, 255000);
                err = regmap_write(regmap, TMP464_TEMP_HYST_REG,
                                   DIV_ROUND_CLOSEST(val, 1000) << 7);
                break;
        case hwmon_temp_max:
                val = temp_to_limit_reg(clamp_val(val, -255000, 255500));
                err = regmap_write(regmap, TMP464_THERM_LIMIT[channel], val);
                break;
        case hwmon_temp_crit:
                val = temp_to_limit_reg(clamp_val(val, -255000, 255500));
                err = regmap_write(regmap, TMP464_THERM2_LIMIT[channel], val);
                break;
        case hwmon_temp_offset:
                val = temp_to_offset_reg(clamp_val(val, -128000, 127937));
                err = regmap_write(regmap, TMP464_TEMP_OFFSET_REG(channel), val);
                break;
        case hwmon_temp_enable:
                data->channel[channel].enabled = !!val;
                err = tmp464_enable_channels(data);
                break;
        default:
                err = -EOPNOTSUPP;
                break;
        }

        return err;
}

static int tmp464_write(struct device *dev, enum hwmon_sensor_types type,
                        u32 attr, int channel, long val)
{
        struct tmp464_data *data = dev_get_drvdata(dev);
        int err;

        switch (type) {
        case hwmon_chip:
                err = tmp464_chip_write(data, attr, channel, val);
                break;
        case hwmon_temp:
                err = tmp464_temp_write(data, attr, channel, val);
                break;
        default:
                err = -EOPNOTSUPP;
                break;
        }

        return err;
}

static umode_t tmp464_is_visible(const void *_data, enum hwmon_sensor_types type,
                                 u32 attr, int channel)
{
        const struct tmp464_data *data = _data;

        if (channel >= data->channels)
                return 0;

        if (type == hwmon_chip) {
                if (attr == hwmon_chip_update_interval)
                        return 0644;
                return 0;
        }

        switch (attr) {
        case hwmon_temp_input:
        case hwmon_temp_max_alarm:
        case hwmon_temp_crit_alarm:
        case hwmon_temp_crit_hyst:
                return 0444;
        case hwmon_temp_enable:
        case hwmon_temp_max:
        case hwmon_temp_crit:
                return 0644;
        case hwmon_temp_max_hyst:
                if (!channel)
                        return 0644;
                return 0444;
        case hwmon_temp_label:
                if (data->channel[channel].label)
                        return 0444;
                return 0;
        case hwmon_temp_fault:
                if (channel)
                        return 0444;
                return 0;
        case hwmon_temp_offset:
                if (channel)
                        return 0644;
                return 0;
        default:
                return 0;
        }
}

static void tmp464_restore_lock(void *regmap)
{
        regmap_write(regmap, TMP464_LOCK_REG, TMP464_LOCK_VAL);
}

static void tmp464_restore_config(void *_data)
{
        struct tmp464_data *data = _data;

        regmap_write(data->regmap, TMP464_CONFIG_REG, data->config_orig);
}

static int tmp464_init_client(struct device *dev, struct tmp464_data *data)
{
        struct regmap *regmap = data->regmap;
        unsigned int regval;
        int err;

        err = regmap_read(regmap, TMP464_LOCK_REG, &regval);
        if (err)
                return err;
        if (regval == TMP464_LOCKED) {
                /* Explicitly unlock chip if it is locked */
                err = regmap_write(regmap, TMP464_LOCK_REG, TMP464_UNLOCK_VAL);
                if (err)
                        return err;
                /* and lock it again when unloading the driver */
                err = devm_add_action_or_reset(dev, tmp464_restore_lock, regmap);
                if (err)
                        return err;
        }

        err = regmap_read(regmap, TMP464_CONFIG_REG, &regval);
        if (err)
                return err;
        data->config_orig = regval;
        err = devm_add_action_or_reset(dev, tmp464_restore_config, data);
        if (err)
                return err;

        /* Default to 500 ms update interval */
        err = regmap_update_bits(regmap, TMP464_CONFIG_REG,
                                 TMP464_CONFIG_CONVERSION_RATE_MASK | TMP464_CONFIG_SHUTDOWN,
                                 BIT(TMP464_CONFIG_CONVERSION_RATE_B0) |
                                 BIT(TMP464_CONFIG_CONVERSION_RATE_B2));
        if (err)
                return err;

        data->update_interval = 500;

        return tmp464_enable_channels(data);
}

static int tmp464_detect(struct i2c_client *client,
                         struct i2c_board_info *info)
{
        struct i2c_adapter *adapter = client->adapter;
        char *name, *chip;
        int reg;

        if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA))
                return -ENODEV;

        reg = i2c_smbus_read_word_swapped(client, TMP464_MANUFACTURER_ID_REG);
        if (reg < 0)
                return reg;
        if (reg != TMP464_MANUFACTURER_ID)
                return -ENODEV;

        /* Check for "always return zero" bits */
        reg = i2c_smbus_read_word_swapped(client, TMP464_THERM_STATUS_REG);
        if (reg < 0)
                return reg;
        if (reg & 0x1f)
                return -ENODEV;
        reg = i2c_smbus_read_word_swapped(client, TMP464_THERM2_STATUS_REG);
        if (reg < 0)
                return reg;
        if (reg & 0x1f)
                return -ENODEV;

        reg = i2c_smbus_read_word_swapped(client, TMP464_DEVICE_ID_REG);
        if (reg < 0)
                return reg;
        switch (reg) {
        case TMP464_DEVICE_ID:
                name = "tmp464";
                chip = "TMP464";
                break;
        case TMP468_DEVICE_ID:
                name = "tmp468";
                chip = "TMP468";
                break;
        default:
                return -ENODEV;
        }

        strscpy(info->type, name, I2C_NAME_SIZE);
        dev_info(&adapter->dev, "Detected TI %s chip at 0x%02x\n", chip, client->addr);

        return 0;
}

static int tmp464_probe_child_from_dt(struct device *dev,
                                      struct device_node *child,
                                      struct tmp464_data *data)

{
        struct regmap *regmap = data->regmap;
        u32 channel;
        s32 nfactor;
        int err;

        err = of_property_read_u32(child, "reg", &channel);
        if (err) {
                dev_err(dev, "missing reg property of %pOFn\n", child);
                return err;
        }

        if (channel >= data->channels) {
                dev_err(dev, "invalid reg %d of %pOFn\n", channel, child);
                return -EINVAL;
        }

        of_property_read_string(child, "label", &data->channel[channel].label);

        data->channel[channel].enabled = of_device_is_available(child);

        err = of_property_read_s32(child, "ti,n-factor", &nfactor);
        if (err && err != -EINVAL)
                return err;
        if (!err) {
                if (channel == 0) {
                        dev_err(dev, "n-factor can't be set for internal channel\n");
                        return -EINVAL;
                }
                if (nfactor > 127 || nfactor < -128) {
                        dev_err(dev, "n-factor for channel %d invalid (%d)\n",
                                channel, nfactor);
                        return -EINVAL;
                }
                err = regmap_write(regmap, TMP464_N_FACTOR_REG(channel),
                                   (nfactor << 8) & 0xff00);
                if (err)
                        return err;
        }

        return 0;
}

static int tmp464_probe_from_dt(struct device *dev, struct tmp464_data *data)
{
        const struct device_node *np = dev->of_node;
        int err;

        for_each_child_of_node_scoped(np, child) {
                if (strcmp(child->name, "channel"))
                        continue;

                err = tmp464_probe_child_from_dt(dev, child, data);
                if (err)
                        return err;
        }

        return 0;
}

static const struct hwmon_ops tmp464_ops = {
        .is_visible = tmp464_is_visible,
        .read = tmp464_read,
        .read_string = tmp464_read_string,
        .write = tmp464_write,
};

static const struct hwmon_channel_info * const tmp464_info[] = {
        HWMON_CHANNEL_INFO(chip,
                           HWMON_C_UPDATE_INTERVAL),
        HWMON_CHANNEL_INFO(temp,
                           HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST | HWMON_T_CRIT |
                           HWMON_T_CRIT_HYST | HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM |
                           HWMON_T_LABEL | HWMON_T_ENABLE,
                           HWMON_T_INPUT | HWMON_T_OFFSET | HWMON_T_MAX | HWMON_T_MAX_HYST |
                           HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MAX_ALARM |
                           HWMON_T_CRIT_ALARM | HWMON_T_FAULT | HWMON_T_LABEL | HWMON_T_ENABLE,
                           HWMON_T_INPUT | HWMON_T_OFFSET | HWMON_T_MAX | HWMON_T_MAX_HYST |
                           HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MAX_ALARM |
                           HWMON_T_CRIT_ALARM | HWMON_T_FAULT | HWMON_T_LABEL | HWMON_T_ENABLE,
                           HWMON_T_INPUT | HWMON_T_OFFSET | HWMON_T_MAX | HWMON_T_MAX_HYST |
                           HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MAX_ALARM |
                           HWMON_T_CRIT_ALARM | HWMON_T_FAULT | HWMON_T_LABEL | HWMON_T_ENABLE,
                           HWMON_T_INPUT | HWMON_T_OFFSET | HWMON_T_MAX | HWMON_T_MAX_HYST |
                           HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MAX_ALARM |
                           HWMON_T_CRIT_ALARM | HWMON_T_FAULT | HWMON_T_LABEL | HWMON_T_ENABLE,
                           HWMON_T_INPUT | HWMON_T_OFFSET | HWMON_T_MAX | HWMON_T_MAX_HYST |
                           HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MAX_ALARM |
                           HWMON_T_CRIT_ALARM | HWMON_T_FAULT | HWMON_T_LABEL | HWMON_T_ENABLE,
                           HWMON_T_INPUT | HWMON_T_OFFSET | HWMON_T_MAX | HWMON_T_MAX_HYST |
                           HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MAX_ALARM |
                           HWMON_T_CRIT_ALARM | HWMON_T_FAULT | HWMON_T_LABEL | HWMON_T_ENABLE,
                           HWMON_T_INPUT | HWMON_T_OFFSET | HWMON_T_MAX | HWMON_T_MAX_HYST |
                           HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MAX_ALARM |
                           HWMON_T_CRIT_ALARM | HWMON_T_FAULT | HWMON_T_LABEL | HWMON_T_ENABLE,
                           HWMON_T_INPUT | HWMON_T_OFFSET | HWMON_T_MAX | HWMON_T_MAX_HYST |
                           HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MAX_ALARM |
                           HWMON_T_CRIT_ALARM | HWMON_T_FAULT | HWMON_T_LABEL | HWMON_T_ENABLE),
        NULL
};

static const struct hwmon_chip_info tmp464_chip_info = {
        .ops = &tmp464_ops,
        .info = tmp464_info,
};

/* regmap */

static bool tmp464_is_volatile_reg(struct device *dev, unsigned int reg)
{
        return (reg < TMP464_TEMP_REG(TMP468_NUM_CHANNELS) ||
                reg == TMP464_THERM_STATUS_REG ||
                reg == TMP464_THERM2_STATUS_REG ||
                reg == TMP464_REMOTE_OPEN_REG);
}

static const struct regmap_config tmp464_regmap_config = {
        .reg_bits = 8,
        .val_bits = 16,
        .max_register = TMP464_DEVICE_ID_REG,
        .volatile_reg = tmp464_is_volatile_reg,
        .val_format_endian = REGMAP_ENDIAN_BIG,
        .cache_type = REGCACHE_MAPLE,
        .use_single_read = true,
        .use_single_write = true,
};

static int tmp464_probe(struct i2c_client *client)
{
        struct device *dev = &client->dev;
        struct device *hwmon_dev;
        struct tmp464_data *data;
        int i, err;

        if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) {
                dev_err(&client->dev, "i2c functionality check failed\n");
                return -ENODEV;
        }
        data = devm_kzalloc(dev, sizeof(struct tmp464_data), GFP_KERNEL);
        if (!data)
                return -ENOMEM;

        data->channels = (int)(unsigned long)i2c_get_match_data(client);

        data->regmap = devm_regmap_init_i2c(client, &tmp464_regmap_config);
        if (IS_ERR(data->regmap))
                return PTR_ERR(data->regmap);

        for (i = 0; i < data->channels; i++)
                data->channel[i].enabled = true;

        err = tmp464_init_client(dev, data);
        if (err)
                return err;

        if (dev->of_node) {
                err = tmp464_probe_from_dt(dev, data);
                if (err)
                        return err;
        }

        hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
                                                         data, &tmp464_chip_info, NULL);
        return PTR_ERR_OR_ZERO(hwmon_dev);
}

static struct i2c_driver tmp464_driver = {
        .class = I2C_CLASS_HWMON,
        .driver = {
                .name   = "tmp464",
                .of_match_table = of_match_ptr(tmp464_of_match),
        },
        .probe = tmp464_probe,
        .id_table = tmp464_id,
        .detect = tmp464_detect,
        .address_list = normal_i2c,
};

module_i2c_driver(tmp464_driver);

MODULE_AUTHOR("Agathe Porte <agathe.porte@nokia.com>");
MODULE_DESCRIPTION("Texas Instruments TMP464 temperature sensor driver");
MODULE_LICENSE("GPL");