8a9947552d
Just about every i2c chip driver sets the I2C_DF_NOTIFY flag, so we can simply make it the default and drop the flag. If any driver really doesn't want to be notified when i2c adapters are added, that driver can simply omit to set .attach_adapter. This approach is also more robust as it prevents accidental NULL pointer dereferences. Signed-off-by: Jean Delvare <khali@linux-fr.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
263 lines
5.9 KiB
C
263 lines
5.9 KiB
C
/*
|
|
* Windfarm PowerMac thermal control. LM75 sensor
|
|
*
|
|
* (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
|
|
* <benh@kernel.crashing.org>
|
|
*
|
|
* Released under the term of the GNU GPL v2.
|
|
*/
|
|
|
|
#include <linux/types.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/init.h>
|
|
#include <linux/wait.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/i2c-dev.h>
|
|
#include <asm/prom.h>
|
|
#include <asm/machdep.h>
|
|
#include <asm/io.h>
|
|
#include <asm/system.h>
|
|
#include <asm/sections.h>
|
|
|
|
#include "windfarm.h"
|
|
|
|
#define VERSION "0.1"
|
|
|
|
#undef DEBUG
|
|
|
|
#ifdef DEBUG
|
|
#define DBG(args...) printk(args)
|
|
#else
|
|
#define DBG(args...) do { } while(0)
|
|
#endif
|
|
|
|
struct wf_lm75_sensor {
|
|
int ds1775 : 1;
|
|
int inited : 1;
|
|
struct i2c_client i2c;
|
|
struct wf_sensor sens;
|
|
};
|
|
#define wf_to_lm75(c) container_of(c, struct wf_lm75_sensor, sens)
|
|
#define i2c_to_lm75(c) container_of(c, struct wf_lm75_sensor, i2c)
|
|
|
|
static int wf_lm75_attach(struct i2c_adapter *adapter);
|
|
static int wf_lm75_detach(struct i2c_client *client);
|
|
|
|
static struct i2c_driver wf_lm75_driver = {
|
|
.owner = THIS_MODULE,
|
|
.name = "wf_lm75",
|
|
.attach_adapter = wf_lm75_attach,
|
|
.detach_client = wf_lm75_detach,
|
|
};
|
|
|
|
static int wf_lm75_get(struct wf_sensor *sr, s32 *value)
|
|
{
|
|
struct wf_lm75_sensor *lm = wf_to_lm75(sr);
|
|
s32 data;
|
|
|
|
if (lm->i2c.adapter == NULL)
|
|
return -ENODEV;
|
|
|
|
/* Init chip if necessary */
|
|
if (!lm->inited) {
|
|
u8 cfg_new, cfg = (u8)i2c_smbus_read_byte_data(&lm->i2c, 1);
|
|
|
|
DBG("wf_lm75: Initializing %s, cfg was: %02x\n",
|
|
sr->name, cfg);
|
|
|
|
/* clear shutdown bit, keep other settings as left by
|
|
* the firmware for now
|
|
*/
|
|
cfg_new = cfg & ~0x01;
|
|
i2c_smbus_write_byte_data(&lm->i2c, 1, cfg_new);
|
|
lm->inited = 1;
|
|
|
|
/* If we just powered it up, let's wait 200 ms */
|
|
msleep(200);
|
|
}
|
|
|
|
/* Read temperature register */
|
|
data = (s32)le16_to_cpu(i2c_smbus_read_word_data(&lm->i2c, 0));
|
|
data <<= 8;
|
|
*value = data;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void wf_lm75_release(struct wf_sensor *sr)
|
|
{
|
|
struct wf_lm75_sensor *lm = wf_to_lm75(sr);
|
|
|
|
/* check if client is registered and detach from i2c */
|
|
if (lm->i2c.adapter) {
|
|
i2c_detach_client(&lm->i2c);
|
|
lm->i2c.adapter = NULL;
|
|
}
|
|
|
|
kfree(lm);
|
|
}
|
|
|
|
static struct wf_sensor_ops wf_lm75_ops = {
|
|
.get_value = wf_lm75_get,
|
|
.release = wf_lm75_release,
|
|
.owner = THIS_MODULE,
|
|
};
|
|
|
|
static struct wf_lm75_sensor *wf_lm75_create(struct i2c_adapter *adapter,
|
|
u8 addr, int ds1775,
|
|
const char *loc)
|
|
{
|
|
struct wf_lm75_sensor *lm;
|
|
|
|
DBG("wf_lm75: creating %s device at address 0x%02x\n",
|
|
ds1775 ? "ds1775" : "lm75", addr);
|
|
|
|
lm = kmalloc(sizeof(struct wf_lm75_sensor), GFP_KERNEL);
|
|
if (lm == NULL)
|
|
return NULL;
|
|
memset(lm, 0, sizeof(struct wf_lm75_sensor));
|
|
|
|
/* Usual rant about sensor names not beeing very consistent in
|
|
* the device-tree, oh well ...
|
|
* Add more entries below as you deal with more setups
|
|
*/
|
|
if (!strcmp(loc, "Hard drive") || !strcmp(loc, "DRIVE BAY"))
|
|
lm->sens.name = "hd-temp";
|
|
else
|
|
goto fail;
|
|
|
|
lm->inited = 0;
|
|
lm->sens.ops = &wf_lm75_ops;
|
|
lm->ds1775 = ds1775;
|
|
lm->i2c.addr = (addr >> 1) & 0x7f;
|
|
lm->i2c.adapter = adapter;
|
|
lm->i2c.driver = &wf_lm75_driver;
|
|
strncpy(lm->i2c.name, lm->sens.name, I2C_NAME_SIZE-1);
|
|
|
|
if (i2c_attach_client(&lm->i2c)) {
|
|
printk(KERN_ERR "windfarm: failed to attach %s %s to i2c\n",
|
|
ds1775 ? "ds1775" : "lm75", lm->i2c.name);
|
|
goto fail;
|
|
}
|
|
|
|
if (wf_register_sensor(&lm->sens)) {
|
|
i2c_detach_client(&lm->i2c);
|
|
goto fail;
|
|
}
|
|
|
|
return lm;
|
|
fail:
|
|
kfree(lm);
|
|
return NULL;
|
|
}
|
|
|
|
static int wf_lm75_attach(struct i2c_adapter *adapter)
|
|
{
|
|
u8 bus_id;
|
|
struct device_node *smu, *bus, *dev;
|
|
|
|
/* We currently only deal with LM75's hanging off the SMU
|
|
* i2c busses. If we extend that driver to other/older
|
|
* machines, we should split this function into SMU-i2c,
|
|
* keywest-i2c, PMU-i2c, ...
|
|
*/
|
|
|
|
DBG("wf_lm75: adapter %s detected\n", adapter->name);
|
|
|
|
if (strncmp(adapter->name, "smu-i2c-", 8) != 0)
|
|
return 0;
|
|
smu = of_find_node_by_type(NULL, "smu");
|
|
if (smu == NULL)
|
|
return 0;
|
|
|
|
/* Look for the bus in the device-tree */
|
|
bus_id = (u8)simple_strtoul(adapter->name + 8, NULL, 16);
|
|
|
|
DBG("wf_lm75: bus ID is %x\n", bus_id);
|
|
|
|
/* Look for sensors subdir */
|
|
for (bus = NULL;
|
|
(bus = of_get_next_child(smu, bus)) != NULL;) {
|
|
u32 *reg;
|
|
|
|
if (strcmp(bus->name, "i2c"))
|
|
continue;
|
|
reg = (u32 *)get_property(bus, "reg", NULL);
|
|
if (reg == NULL)
|
|
continue;
|
|
if (bus_id == *reg)
|
|
break;
|
|
}
|
|
of_node_put(smu);
|
|
if (bus == NULL) {
|
|
printk(KERN_WARNING "windfarm: SMU i2c bus 0x%x not found"
|
|
" in device-tree !\n", bus_id);
|
|
return 0;
|
|
}
|
|
|
|
DBG("wf_lm75: bus found, looking for device...\n");
|
|
|
|
/* Now look for lm75(s) in there */
|
|
for (dev = NULL;
|
|
(dev = of_get_next_child(bus, dev)) != NULL;) {
|
|
const char *loc =
|
|
get_property(dev, "hwsensor-location", NULL);
|
|
u32 *reg = (u32 *)get_property(dev, "reg", NULL);
|
|
DBG(" dev: %s... (loc: %p, reg: %p)\n", dev->name, loc, reg);
|
|
if (loc == NULL || reg == NULL)
|
|
continue;
|
|
/* real lm75 */
|
|
if (device_is_compatible(dev, "lm75"))
|
|
wf_lm75_create(adapter, *reg, 0, loc);
|
|
/* ds1775 (compatible, better resolution */
|
|
else if (device_is_compatible(dev, "ds1775"))
|
|
wf_lm75_create(adapter, *reg, 1, loc);
|
|
}
|
|
|
|
of_node_put(bus);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int wf_lm75_detach(struct i2c_client *client)
|
|
{
|
|
struct wf_lm75_sensor *lm = i2c_to_lm75(client);
|
|
|
|
DBG("wf_lm75: i2c detatch called for %s\n", lm->sens.name);
|
|
|
|
/* Mark client detached */
|
|
lm->i2c.adapter = NULL;
|
|
|
|
/* release sensor */
|
|
wf_unregister_sensor(&lm->sens);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __init wf_lm75_sensor_init(void)
|
|
{
|
|
int rc;
|
|
|
|
rc = i2c_add_driver(&wf_lm75_driver);
|
|
if (rc < 0)
|
|
return rc;
|
|
return 0;
|
|
}
|
|
|
|
static void __exit wf_lm75_sensor_exit(void)
|
|
{
|
|
i2c_del_driver(&wf_lm75_driver);
|
|
}
|
|
|
|
|
|
module_init(wf_lm75_sensor_init);
|
|
module_exit(wf_lm75_sensor_exit);
|
|
|
|
MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
|
|
MODULE_DESCRIPTION("LM75 sensor objects for PowerMacs thermal control");
|
|
MODULE_LICENSE("GPL");
|
|
|