mirror of
https://github.com/FEX-Emu/linux.git
synced 2024-12-21 00:42:16 +00:00
9ebd3d822e
More LM75 updates: - Teach the LM75 driver to use new-style driver binding: * Create a second driver struct, using new-style driver binding methods cribbed from the legacy code. * Add a MODULE_DEVICE_TABLE (for "newER-style binding") * The legacy probe logic delegates its work to this new code. * The legacy driver now uses the name "lm75_legacy". - More careful initialization. Chips are put into 9-bit mode so the current interconversion routines will never fail. - Save the original chip configuration, and restore it on exit. (Among other things, this normally turns off the mode where the chip is constantly sampling ... and thus saves power.) So the new-style code should catch all chips that boards declare, while the legacy code catches others. This particular coexistence strategy may need some work yet ... legacy modes might best be set up explicitly by some tool not unlike "sensors-detect". (Or else completely eradicated...) Signed-off-by: David Brownell <dbrownell@users.sourceforge.net> Acked-by: Jean Delvare <khali@linux-fr.org> Signed-off-by: Mark M. Hoffman <mhoffman@lightlink.com>
452 lines
12 KiB
C
452 lines
12 KiB
C
/*
|
|
lm75.c - Part of lm_sensors, Linux kernel modules for hardware
|
|
monitoring
|
|
Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl>
|
|
|
|
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., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/jiffies.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/hwmon.h>
|
|
#include <linux/hwmon-sysfs.h>
|
|
#include <linux/err.h>
|
|
#include <linux/mutex.h>
|
|
#include "lm75.h"
|
|
|
|
|
|
/*
|
|
* This driver handles the LM75 and compatible digital temperature sensors.
|
|
* Only types which are _not_ listed in I2C_CLIENT_INSMOD_*() need to be
|
|
* listed here. We start at 9 since I2C_CLIENT_INSMOD_*() currently allow
|
|
* definition of up to 8 chip types (plus zero).
|
|
*/
|
|
|
|
enum lm75_type { /* keep sorted in alphabetical order */
|
|
ds1775 = 9,
|
|
ds75,
|
|
/* lm75 -- in I2C_CLIENT_INSMOD_1() */
|
|
lm75a,
|
|
max6625,
|
|
max6626,
|
|
mcp980x,
|
|
stds75,
|
|
tcn75,
|
|
tmp100,
|
|
tmp101,
|
|
tmp175,
|
|
tmp275,
|
|
tmp75,
|
|
};
|
|
|
|
/* Addresses scanned by legacy style driver binding */
|
|
static const unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4b, 0x4c,
|
|
0x4d, 0x4e, 0x4f, I2C_CLIENT_END };
|
|
|
|
/* Insmod parameters (only for legacy style driver binding) */
|
|
I2C_CLIENT_INSMOD_1(lm75);
|
|
|
|
|
|
/* The LM75 registers */
|
|
#define LM75_REG_CONF 0x01
|
|
static const u8 LM75_REG_TEMP[3] = {
|
|
0x00, /* input */
|
|
0x03, /* max */
|
|
0x02, /* hyst */
|
|
};
|
|
|
|
/* Each client has this additional data */
|
|
struct lm75_data {
|
|
struct i2c_client *client;
|
|
struct device *hwmon_dev;
|
|
struct mutex update_lock;
|
|
u8 orig_conf;
|
|
char valid; /* !=0 if registers are valid */
|
|
unsigned long last_updated; /* In jiffies */
|
|
u16 temp[3]; /* Register values,
|
|
0 = input
|
|
1 = max
|
|
2 = hyst */
|
|
};
|
|
|
|
static int lm75_read_value(struct i2c_client *client, u8 reg);
|
|
static int lm75_write_value(struct i2c_client *client, u8 reg, u16 value);
|
|
static struct lm75_data *lm75_update_device(struct device *dev);
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
|
|
/* sysfs attributes for hwmon */
|
|
|
|
static ssize_t show_temp(struct device *dev, struct device_attribute *da,
|
|
char *buf)
|
|
{
|
|
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
|
struct lm75_data *data = lm75_update_device(dev);
|
|
return sprintf(buf, "%d\n",
|
|
LM75_TEMP_FROM_REG(data->temp[attr->index]));
|
|
}
|
|
|
|
static ssize_t set_temp(struct device *dev, struct device_attribute *da,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct lm75_data *data = i2c_get_clientdata(client);
|
|
int nr = attr->index;
|
|
long temp = simple_strtol(buf, NULL, 10);
|
|
|
|
mutex_lock(&data->update_lock);
|
|
data->temp[nr] = LM75_TEMP_TO_REG(temp);
|
|
lm75_write_value(client, LM75_REG_TEMP[nr], data->temp[nr]);
|
|
mutex_unlock(&data->update_lock);
|
|
return count;
|
|
}
|
|
|
|
static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO,
|
|
show_temp, set_temp, 1);
|
|
static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO,
|
|
show_temp, set_temp, 2);
|
|
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0);
|
|
|
|
static struct attribute *lm75_attributes[] = {
|
|
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
|
&sensor_dev_attr_temp1_max.dev_attr.attr,
|
|
&sensor_dev_attr_temp1_max_hyst.dev_attr.attr,
|
|
|
|
NULL
|
|
};
|
|
|
|
static const struct attribute_group lm75_group = {
|
|
.attrs = lm75_attributes,
|
|
};
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
|
|
/* "New style" I2C driver binding -- following the driver model */
|
|
|
|
static int
|
|
lm75_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
|
{
|
|
struct lm75_data *data;
|
|
int status;
|
|
u8 set_mask, clr_mask;
|
|
int new;
|
|
|
|
if (!i2c_check_functionality(client->adapter,
|
|
I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA))
|
|
return -EIO;
|
|
|
|
data = kzalloc(sizeof(struct lm75_data), GFP_KERNEL);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
i2c_set_clientdata(client, data);
|
|
|
|
data->client = client;
|
|
mutex_init(&data->update_lock);
|
|
|
|
/* Set to LM75 resolution (9 bits, 1/2 degree C) and range.
|
|
* Then tweak to be more precise when appropriate.
|
|
*/
|
|
set_mask = 0;
|
|
clr_mask = (1 << 0) /* continuous conversions */
|
|
| (1 << 6) | (1 << 5); /* 9-bit mode */
|
|
|
|
/* configure as specified */
|
|
status = lm75_read_value(client, LM75_REG_CONF);
|
|
if (status < 0) {
|
|
dev_dbg(&client->dev, "Can't read config? %d\n", status);
|
|
goto exit_free;
|
|
}
|
|
data->orig_conf = status;
|
|
new = status & ~clr_mask;
|
|
new |= set_mask;
|
|
if (status != new)
|
|
lm75_write_value(client, LM75_REG_CONF, new);
|
|
dev_dbg(&client->dev, "Config %02x\n", new);
|
|
|
|
/* Register sysfs hooks */
|
|
status = sysfs_create_group(&client->dev.kobj, &lm75_group);
|
|
if (status)
|
|
goto exit_free;
|
|
|
|
data->hwmon_dev = hwmon_device_register(&client->dev);
|
|
if (IS_ERR(data->hwmon_dev)) {
|
|
status = PTR_ERR(data->hwmon_dev);
|
|
goto exit_remove;
|
|
}
|
|
|
|
dev_info(&client->dev, "%s: sensor '%s'\n",
|
|
data->hwmon_dev->bus_id, client->name);
|
|
|
|
return 0;
|
|
|
|
exit_remove:
|
|
sysfs_remove_group(&client->dev.kobj, &lm75_group);
|
|
exit_free:
|
|
i2c_set_clientdata(client, NULL);
|
|
kfree(data);
|
|
return status;
|
|
}
|
|
|
|
static int lm75_remove(struct i2c_client *client)
|
|
{
|
|
struct lm75_data *data = i2c_get_clientdata(client);
|
|
|
|
hwmon_device_unregister(data->hwmon_dev);
|
|
sysfs_remove_group(&client->dev.kobj, &lm75_group);
|
|
lm75_write_value(client, LM75_REG_CONF, data->orig_conf);
|
|
i2c_set_clientdata(client, NULL);
|
|
kfree(data);
|
|
return 0;
|
|
}
|
|
|
|
static const struct i2c_device_id lm75_ids[] = {
|
|
{ "ds1775", ds1775, },
|
|
{ "ds75", ds75, },
|
|
{ "lm75", lm75, },
|
|
{ "lm75a", lm75a, },
|
|
{ "max6625", max6625, },
|
|
{ "max6626", max6626, },
|
|
{ "mcp980x", mcp980x, },
|
|
{ "stds75", stds75, },
|
|
{ "tcn75", tcn75, },
|
|
{ "tmp100", tmp100, },
|
|
{ "tmp101", tmp101, },
|
|
{ "tmp175", tmp175, },
|
|
{ "tmp275", tmp275, },
|
|
{ "tmp75", tmp75, },
|
|
{ /* LIST END */ }
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, lm75_ids);
|
|
|
|
static struct i2c_driver lm75_driver = {
|
|
.driver = {
|
|
.name = "lm75",
|
|
},
|
|
.probe = lm75_probe,
|
|
.remove = lm75_remove,
|
|
.id_table = lm75_ids,
|
|
};
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
|
|
/* "Legacy" I2C driver binding */
|
|
|
|
static struct i2c_driver lm75_legacy_driver;
|
|
|
|
/* This function is called by i2c_probe */
|
|
static int lm75_detect(struct i2c_adapter *adapter, int address, int kind)
|
|
{
|
|
int i;
|
|
struct i2c_client *new_client;
|
|
int err = 0;
|
|
|
|
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA |
|
|
I2C_FUNC_SMBUS_WORD_DATA))
|
|
goto exit;
|
|
|
|
/* OK. For now, we presume we have a valid address. We create the
|
|
client structure, even though there may be no sensor present.
|
|
But it allows us to use i2c_smbus_read_*_data() calls. */
|
|
new_client = kzalloc(sizeof *new_client, GFP_KERNEL);
|
|
if (!new_client) {
|
|
err = -ENOMEM;
|
|
goto exit;
|
|
}
|
|
|
|
new_client->addr = address;
|
|
new_client->adapter = adapter;
|
|
new_client->driver = &lm75_legacy_driver;
|
|
new_client->flags = 0;
|
|
|
|
/* Now, we do the remaining detection. There is no identification-
|
|
dedicated register so we have to rely on several tricks:
|
|
unused bits, registers cycling over 8-address boundaries,
|
|
addresses 0x04-0x07 returning the last read value.
|
|
The cycling+unused addresses combination is not tested,
|
|
since it would significantly slow the detection down and would
|
|
hardly add any value. */
|
|
if (kind < 0) {
|
|
int cur, conf, hyst, os;
|
|
|
|
/* Unused addresses */
|
|
cur = i2c_smbus_read_word_data(new_client, 0);
|
|
conf = i2c_smbus_read_byte_data(new_client, 1);
|
|
hyst = i2c_smbus_read_word_data(new_client, 2);
|
|
if (i2c_smbus_read_word_data(new_client, 4) != hyst
|
|
|| i2c_smbus_read_word_data(new_client, 5) != hyst
|
|
|| i2c_smbus_read_word_data(new_client, 6) != hyst
|
|
|| i2c_smbus_read_word_data(new_client, 7) != hyst)
|
|
goto exit_free;
|
|
os = i2c_smbus_read_word_data(new_client, 3);
|
|
if (i2c_smbus_read_word_data(new_client, 4) != os
|
|
|| i2c_smbus_read_word_data(new_client, 5) != os
|
|
|| i2c_smbus_read_word_data(new_client, 6) != os
|
|
|| i2c_smbus_read_word_data(new_client, 7) != os)
|
|
goto exit_free;
|
|
|
|
/* Unused bits */
|
|
if (conf & 0xe0)
|
|
goto exit_free;
|
|
|
|
/* Addresses cycling */
|
|
for (i = 8; i < 0xff; i += 8)
|
|
if (i2c_smbus_read_byte_data(new_client, i + 1) != conf
|
|
|| i2c_smbus_read_word_data(new_client, i + 2) != hyst
|
|
|| i2c_smbus_read_word_data(new_client, i + 3) != os)
|
|
goto exit_free;
|
|
}
|
|
|
|
/* NOTE: we treat "force=..." and "force_lm75=..." the same.
|
|
* Only new-style driver binding distinguishes chip types.
|
|
*/
|
|
strlcpy(new_client->name, "lm75", I2C_NAME_SIZE);
|
|
|
|
/* Tell the I2C layer a new client has arrived */
|
|
err = i2c_attach_client(new_client);
|
|
if (err)
|
|
goto exit_free;
|
|
|
|
err = lm75_probe(new_client, NULL);
|
|
if (err < 0)
|
|
goto exit_detach;
|
|
|
|
return 0;
|
|
|
|
exit_detach:
|
|
i2c_detach_client(new_client);
|
|
exit_free:
|
|
kfree(new_client);
|
|
exit:
|
|
return err;
|
|
}
|
|
|
|
static int lm75_attach_adapter(struct i2c_adapter *adapter)
|
|
{
|
|
if (!(adapter->class & I2C_CLASS_HWMON))
|
|
return 0;
|
|
return i2c_probe(adapter, &addr_data, lm75_detect);
|
|
}
|
|
|
|
static int lm75_detach_client(struct i2c_client *client)
|
|
{
|
|
lm75_remove(client);
|
|
i2c_detach_client(client);
|
|
kfree(client);
|
|
return 0;
|
|
}
|
|
|
|
static struct i2c_driver lm75_legacy_driver = {
|
|
.driver = {
|
|
.name = "lm75_legacy",
|
|
},
|
|
.attach_adapter = lm75_attach_adapter,
|
|
.detach_client = lm75_detach_client,
|
|
};
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
|
|
/* register access */
|
|
|
|
/* All registers are word-sized, except for the configuration register.
|
|
LM75 uses a high-byte first convention, which is exactly opposite to
|
|
the SMBus standard. */
|
|
static int lm75_read_value(struct i2c_client *client, u8 reg)
|
|
{
|
|
int value;
|
|
|
|
if (reg == LM75_REG_CONF)
|
|
return i2c_smbus_read_byte_data(client, reg);
|
|
|
|
value = i2c_smbus_read_word_data(client, reg);
|
|
return (value < 0) ? value : swab16(value);
|
|
}
|
|
|
|
static int lm75_write_value(struct i2c_client *client, u8 reg, u16 value)
|
|
{
|
|
if (reg == LM75_REG_CONF)
|
|
return i2c_smbus_write_byte_data(client, reg, value);
|
|
else
|
|
return i2c_smbus_write_word_data(client, reg, swab16(value));
|
|
}
|
|
|
|
static struct lm75_data *lm75_update_device(struct device *dev)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct lm75_data *data = i2c_get_clientdata(client);
|
|
|
|
mutex_lock(&data->update_lock);
|
|
|
|
if (time_after(jiffies, data->last_updated + HZ + HZ / 2)
|
|
|| !data->valid) {
|
|
int i;
|
|
dev_dbg(&client->dev, "Starting lm75 update\n");
|
|
|
|
for (i = 0; i < ARRAY_SIZE(data->temp); i++) {
|
|
int status;
|
|
|
|
status = lm75_read_value(client, LM75_REG_TEMP[i]);
|
|
if (status < 0)
|
|
dev_dbg(&client->dev, "reg %d, err %d\n",
|
|
LM75_REG_TEMP[i], status);
|
|
else
|
|
data->temp[i] = status;
|
|
}
|
|
data->last_updated = jiffies;
|
|
data->valid = 1;
|
|
}
|
|
|
|
mutex_unlock(&data->update_lock);
|
|
|
|
return data;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
|
|
/* module glue */
|
|
|
|
static int __init sensors_lm75_init(void)
|
|
{
|
|
int status;
|
|
|
|
status = i2c_add_driver(&lm75_driver);
|
|
if (status < 0)
|
|
return status;
|
|
|
|
status = i2c_add_driver(&lm75_legacy_driver);
|
|
if (status < 0)
|
|
i2c_del_driver(&lm75_driver);
|
|
|
|
return status;
|
|
}
|
|
|
|
static void __exit sensors_lm75_exit(void)
|
|
{
|
|
i2c_del_driver(&lm75_legacy_driver);
|
|
i2c_del_driver(&lm75_driver);
|
|
}
|
|
|
|
MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl>");
|
|
MODULE_DESCRIPTION("LM75 driver");
|
|
MODULE_LICENSE("GPL");
|
|
|
|
module_init(sensors_lm75_init);
|
|
module_exit(sensors_lm75_exit);
|