Merge branch 'hwmon-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jdelvare/staging
* 'hwmon-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jdelvare/staging: hwmon: (lm78) Become the maintainer hwmon: (lm78) Make ISA interface depend on CONFIG_ISA hwmon: (lm78) Avoid forward declarations hwmon: (sht15) Correct a comment mistake hwmon: (max1111) Avoid extra memory allocations hwmon: (it87) Add chassis intrusion detection support hwmon: (via-cputemp) Add VID reporting support hwmon-vid: Add support for VIA family 6 model D CPU hwmon: New driver sch5636 hwmon: (sch5627) Factor out some code shared with sch5636 driver
This commit is contained in:
commit
750e06992d
|
@ -76,7 +76,8 @@ IT8718F, IT8720F, IT8721F, IT8726F, IT8758E and SiS950 chips.
|
|||
These chips are 'Super I/O chips', supporting floppy disks, infrared ports,
|
||||
joysticks and other miscellaneous stuff. For hardware monitoring, they
|
||||
include an 'environment controller' with 3 temperature sensors, 3 fan
|
||||
rotation speed sensors, 8 voltage sensors, and associated alarms.
|
||||
rotation speed sensors, 8 voltage sensors, associated alarms, and chassis
|
||||
intrusion detection.
|
||||
|
||||
The IT8712F and IT8716F additionally feature VID inputs, used to report
|
||||
the Vcore voltage of the processor. The early IT8712F have 5 VID pins,
|
||||
|
|
|
@ -13,7 +13,8 @@ Supported chips:
|
|||
Datasheet: Publicly available at the National Semiconductor website
|
||||
http://www.national.com/
|
||||
|
||||
Author: Frodo Looijaard <frodol@dds.nl>
|
||||
Authors: Frodo Looijaard <frodol@dds.nl>
|
||||
Jean Delvare <khali@linux-fr.org>
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
Kernel driver sch5636
|
||||
=====================
|
||||
|
||||
Supported chips:
|
||||
* SMSC SCH5636
|
||||
Prefix: 'sch5636'
|
||||
Addresses scanned: none, address read from Super I/O config space
|
||||
|
||||
Author: Hans de Goede <hdegoede@redhat.com>
|
||||
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
SMSC SCH5636 Super I/O chips include an embedded microcontroller for
|
||||
hardware monitoring solutions, allowing motherboard manufacturers to create
|
||||
their own custom hwmon solution based upon the SCH5636.
|
||||
|
||||
Currently the sch5636 driver only supports the Fujitsu Theseus SCH5636 based
|
||||
hwmon solution. The sch5636 driver runs a sanity check on loading to ensure
|
||||
it is dealing with a Fujitsu Theseus and not with another custom SCH5636 based
|
||||
hwmon solution.
|
||||
|
||||
The Fujitsu Theseus can monitor up to 5 voltages, 8 fans and 16
|
||||
temperatures. Note that the driver detects how many fan headers /
|
||||
temperature sensors are actually implemented on the motherboard, so you will
|
||||
likely see fewer temperature and fan inputs.
|
||||
|
||||
An application note describing the Theseus' registers, as well as an
|
||||
application note describing the protocol for communicating with the
|
||||
microcontroller is available upon request. Please mail me if you want a copy.
|
|
@ -3960,6 +3960,13 @@ L: lm-sensors@lm-sensors.org
|
|||
S: Maintained
|
||||
F: drivers/hwmon/lm73.c
|
||||
|
||||
LM78 HARDWARE MONITOR DRIVER
|
||||
M: Jean Delvare <khali@linux-fr.org>
|
||||
L: lm-sensors@lm-sensors.org
|
||||
S: Maintained
|
||||
F: Documentation/hwmon/lm78
|
||||
F: drivers/hwmon/lm78.c
|
||||
|
||||
LM83 HARDWARE MONITOR DRIVER
|
||||
M: Jean Delvare <khali@linux-fr.org>
|
||||
L: lm-sensors@lm-sensors.org
|
||||
|
|
|
@ -1041,8 +1041,13 @@ config SENSORS_SMSC47B397
|
|||
This driver can also be built as a module. If so, the module
|
||||
will be called smsc47b397.
|
||||
|
||||
config SENSORS_SCH56XX_COMMON
|
||||
tristate
|
||||
default n
|
||||
|
||||
config SENSORS_SCH5627
|
||||
tristate "SMSC SCH5627"
|
||||
select SENSORS_SCH56XX_COMMON
|
||||
help
|
||||
If you say yes here you get support for the hardware monitoring
|
||||
features of the SMSC SCH5627 Super-I/O chip.
|
||||
|
@ -1050,6 +1055,21 @@ config SENSORS_SCH5627
|
|||
This driver can also be built as a module. If so, the module
|
||||
will be called sch5627.
|
||||
|
||||
config SENSORS_SCH5636
|
||||
tristate "SMSC SCH5636"
|
||||
select SENSORS_SCH56XX_COMMON
|
||||
help
|
||||
SMSC SCH5636 Super I/O chips include an embedded microcontroller for
|
||||
hardware monitoring solutions, allowing motherboard manufacturers to
|
||||
create their own custom hwmon solution based upon the SCH5636.
|
||||
|
||||
Currently this driver only supports the Fujitsu Theseus SCH5636 based
|
||||
hwmon solution. Say yes here if you want support for the Fujitsu
|
||||
Theseus' hardware monitoring features.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called sch5636.
|
||||
|
||||
config SENSORS_ADS1015
|
||||
tristate "Texas Instruments ADS1015"
|
||||
depends on I2C
|
||||
|
@ -1142,6 +1162,7 @@ config SENSORS_TWL4030_MADC
|
|||
config SENSORS_VIA_CPUTEMP
|
||||
tristate "VIA CPU temperature sensor"
|
||||
depends on X86
|
||||
select HWMON_VID
|
||||
help
|
||||
If you say yes here you get support for the temperature
|
||||
sensor inside your CPU. Supported are all known variants of
|
||||
|
|
|
@ -95,7 +95,9 @@ obj-$(CONFIG_SENSORS_PC87360) += pc87360.o
|
|||
obj-$(CONFIG_SENSORS_PC87427) += pc87427.o
|
||||
obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o
|
||||
obj-$(CONFIG_SENSORS_S3C) += s3c-hwmon.o
|
||||
obj-$(CONFIG_SENSORS_SCH56XX_COMMON)+= sch56xx-common.o
|
||||
obj-$(CONFIG_SENSORS_SCH5627) += sch5627.o
|
||||
obj-$(CONFIG_SENSORS_SCH5636) += sch5636.o
|
||||
obj-$(CONFIG_SENSORS_SHT15) += sht15.o
|
||||
obj-$(CONFIG_SENSORS_SHT21) += sht21.o
|
||||
obj-$(CONFIG_SENSORS_SIS5595) += sis5595.o
|
||||
|
|
|
@ -140,7 +140,11 @@ int vid_from_reg(int val, u8 vrm)
|
|||
return(val & 0x10 ? 975 - (val & 0xF) * 25 :
|
||||
1750 - val * 50);
|
||||
case 13:
|
||||
case 131:
|
||||
val &= 0x3f;
|
||||
/* Exception for Eden ULV 500 MHz */
|
||||
if (vrm == 131 && val == 0x3f)
|
||||
val++;
|
||||
return(1708 - val * 16);
|
||||
case 14: /* Intel Core */
|
||||
/* compute in uV, round to mV */
|
||||
|
@ -205,11 +209,45 @@ static struct vrm_model vrm_models[] = {
|
|||
{X86_VENDOR_CENTAUR, 0x6, 0x9, 0x7, 85}, /* Nehemiah */
|
||||
{X86_VENDOR_CENTAUR, 0x6, 0x9, ANY, 17}, /* C3-M, Eden-N */
|
||||
{X86_VENDOR_CENTAUR, 0x6, 0xA, 0x7, 0}, /* No information */
|
||||
{X86_VENDOR_CENTAUR, 0x6, 0xA, ANY, 13}, /* C7, Esther */
|
||||
{X86_VENDOR_CENTAUR, 0x6, 0xA, ANY, 13}, /* C7-M, C7, Eden (Esther) */
|
||||
{X86_VENDOR_CENTAUR, 0x6, 0xD, ANY, 134}, /* C7-D, C7-M, C7, Eden (Esther) */
|
||||
|
||||
{X86_VENDOR_UNKNOWN, ANY, ANY, ANY, 0} /* stop here */
|
||||
};
|
||||
|
||||
/*
|
||||
* Special case for VIA model D: there are two different possible
|
||||
* VID tables, so we have to figure out first, which one must be
|
||||
* used. This resolves temporary drm value 134 to 14 (Intel Core
|
||||
* 7-bit VID), 13 (Pentium M 6-bit VID) or 131 (Pentium M 6-bit VID
|
||||
* + quirk for Eden ULV 500 MHz).
|
||||
* Note: something similar might be needed for model A, I'm not sure.
|
||||
*/
|
||||
static u8 get_via_model_d_vrm(void)
|
||||
{
|
||||
unsigned int vid, brand, dummy;
|
||||
static const char *brands[4] = {
|
||||
"C7-M", "C7", "Eden", "C7-D"
|
||||
};
|
||||
|
||||
rdmsr(0x198, dummy, vid);
|
||||
vid &= 0xff;
|
||||
|
||||
rdmsr(0x1154, brand, dummy);
|
||||
brand = ((brand >> 4) ^ (brand >> 2)) & 0x03;
|
||||
|
||||
if (vid > 0x3f) {
|
||||
pr_info("Using %d-bit VID table for VIA %s CPU\n",
|
||||
7, brands[brand]);
|
||||
return 14;
|
||||
} else {
|
||||
pr_info("Using %d-bit VID table for VIA %s CPU\n",
|
||||
6, brands[brand]);
|
||||
/* Enable quirk for Eden */
|
||||
return brand == 2 ? 131 : 13;
|
||||
}
|
||||
}
|
||||
|
||||
static u8 find_vrm(u8 eff_family, u8 eff_model, u8 eff_stepping, u8 vendor)
|
||||
{
|
||||
int i = 0;
|
||||
|
@ -247,6 +285,8 @@ u8 vid_which_vrm(void)
|
|||
eff_model += ((eax & 0x000F0000)>>16)<<4;
|
||||
}
|
||||
vrm_ret = find_vrm(eff_family, eff_model, eff_stepping, c->x86_vendor);
|
||||
if (vrm_ret == 134)
|
||||
vrm_ret = get_via_model_d_vrm();
|
||||
if (vrm_ret == 0)
|
||||
pr_info("Unknown VRM version of your x86 CPU\n");
|
||||
return vrm_ret;
|
||||
|
|
|
@ -1172,6 +1172,32 @@ static ssize_t show_alarm(struct device *dev, struct device_attribute *attr,
|
|||
struct it87_data *data = it87_update_device(dev);
|
||||
return sprintf(buf, "%u\n", (data->alarms >> bitnr) & 1);
|
||||
}
|
||||
|
||||
static ssize_t clear_intrusion(struct device *dev, struct device_attribute
|
||||
*attr, const char *buf, size_t count)
|
||||
{
|
||||
struct it87_data *data = dev_get_drvdata(dev);
|
||||
long val;
|
||||
int config;
|
||||
|
||||
if (strict_strtol(buf, 10, &val) < 0 || val != 0)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
config = it87_read_value(data, IT87_REG_CONFIG);
|
||||
if (config < 0) {
|
||||
count = config;
|
||||
} else {
|
||||
config |= 1 << 5;
|
||||
it87_write_value(data, IT87_REG_CONFIG, config);
|
||||
/* Invalidate cache to force re-read */
|
||||
data->valid = 0;
|
||||
}
|
||||
mutex_unlock(&data->update_lock);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static SENSOR_DEVICE_ATTR(in0_alarm, S_IRUGO, show_alarm, NULL, 8);
|
||||
static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, show_alarm, NULL, 9);
|
||||
static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, show_alarm, NULL, 10);
|
||||
|
@ -1188,6 +1214,8 @@ static SENSOR_DEVICE_ATTR(fan5_alarm, S_IRUGO, show_alarm, NULL, 6);
|
|||
static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 16);
|
||||
static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL, 17);
|
||||
static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO, show_alarm, NULL, 18);
|
||||
static SENSOR_DEVICE_ATTR(intrusion0_alarm, S_IRUGO | S_IWUSR,
|
||||
show_alarm, clear_intrusion, 4);
|
||||
|
||||
static ssize_t show_beep(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
|
@ -1350,6 +1378,7 @@ static struct attribute *it87_attributes[] = {
|
|||
&sensor_dev_attr_temp3_alarm.dev_attr.attr,
|
||||
|
||||
&dev_attr_alarms.attr,
|
||||
&sensor_dev_attr_intrusion0_alarm.dev_attr.attr,
|
||||
&dev_attr_name.attr,
|
||||
NULL
|
||||
};
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
lm78.c - Part of lm_sensors, Linux kernel modules for hardware
|
||||
monitoring
|
||||
Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl>
|
||||
Copyright (c) 2007 Jean Delvare <khali@linux-fr.org>
|
||||
Copyright (c) 2007, 2011 Jean Delvare <khali@linux-fr.org>
|
||||
|
||||
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
|
||||
|
@ -26,23 +26,21 @@
|
|||
#include <linux/slab.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/hwmon-vid.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/io.h>
|
||||
|
||||
/* ISA device, if found */
|
||||
static struct platform_device *pdev;
|
||||
#ifdef CONFIG_ISA
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/io.h>
|
||||
#endif
|
||||
|
||||
/* Addresses to scan */
|
||||
static const unsigned short normal_i2c[] = { 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d,
|
||||
0x2e, 0x2f, I2C_CLIENT_END };
|
||||
static unsigned short isa_address = 0x290;
|
||||
|
||||
enum chips { lm78, lm79 };
|
||||
|
||||
/* Many LM78 constants specified below */
|
||||
|
@ -143,50 +141,12 @@ struct lm78_data {
|
|||
};
|
||||
|
||||
|
||||
static int lm78_i2c_detect(struct i2c_client *client,
|
||||
struct i2c_board_info *info);
|
||||
static int lm78_i2c_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id);
|
||||
static int lm78_i2c_remove(struct i2c_client *client);
|
||||
|
||||
static int __devinit lm78_isa_probe(struct platform_device *pdev);
|
||||
static int __devexit lm78_isa_remove(struct platform_device *pdev);
|
||||
|
||||
static int lm78_read_value(struct lm78_data *data, u8 reg);
|
||||
static int lm78_write_value(struct lm78_data *data, u8 reg, u8 value);
|
||||
static struct lm78_data *lm78_update_device(struct device *dev);
|
||||
static void lm78_init_device(struct lm78_data *data);
|
||||
|
||||
|
||||
static const struct i2c_device_id lm78_i2c_id[] = {
|
||||
{ "lm78", lm78 },
|
||||
{ "lm79", lm79 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, lm78_i2c_id);
|
||||
|
||||
static struct i2c_driver lm78_driver = {
|
||||
.class = I2C_CLASS_HWMON,
|
||||
.driver = {
|
||||
.name = "lm78",
|
||||
},
|
||||
.probe = lm78_i2c_probe,
|
||||
.remove = lm78_i2c_remove,
|
||||
.id_table = lm78_i2c_id,
|
||||
.detect = lm78_i2c_detect,
|
||||
.address_list = normal_i2c,
|
||||
};
|
||||
|
||||
static struct platform_driver lm78_isa_driver = {
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "lm78",
|
||||
},
|
||||
.probe = lm78_isa_probe,
|
||||
.remove = __devexit_p(lm78_isa_remove),
|
||||
};
|
||||
|
||||
|
||||
/* 7 Voltages */
|
||||
static ssize_t show_in(struct device *dev, struct device_attribute *da,
|
||||
char *buf)
|
||||
|
@ -514,6 +474,16 @@ static const struct attribute_group lm78_group = {
|
|||
.attrs = lm78_attributes,
|
||||
};
|
||||
|
||||
/*
|
||||
* ISA related code
|
||||
*/
|
||||
#ifdef CONFIG_ISA
|
||||
|
||||
/* ISA device, if found */
|
||||
static struct platform_device *pdev;
|
||||
|
||||
static unsigned short isa_address = 0x290;
|
||||
|
||||
/* I2C devices get this name attribute automatically, but for ISA devices
|
||||
we must create it by ourselves. */
|
||||
static ssize_t show_name(struct device *dev, struct device_attribute
|
||||
|
@ -525,6 +495,11 @@ static ssize_t show_name(struct device *dev, struct device_attribute
|
|||
}
|
||||
static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
|
||||
|
||||
static struct lm78_data *lm78_data_if_isa(void)
|
||||
{
|
||||
return pdev ? platform_get_drvdata(pdev) : NULL;
|
||||
}
|
||||
|
||||
/* Returns 1 if the I2C chip appears to be an alias of the ISA chip */
|
||||
static int lm78_alias_detect(struct i2c_client *client, u8 chipid)
|
||||
{
|
||||
|
@ -558,12 +533,24 @@ static int lm78_alias_detect(struct i2c_client *client, u8 chipid)
|
|||
|
||||
return 1;
|
||||
}
|
||||
#else /* !CONFIG_ISA */
|
||||
|
||||
static int lm78_alias_detect(struct i2c_client *client, u8 chipid)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct lm78_data *lm78_data_if_isa(void)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
#endif /* CONFIG_ISA */
|
||||
|
||||
static int lm78_i2c_detect(struct i2c_client *client,
|
||||
struct i2c_board_info *info)
|
||||
{
|
||||
int i;
|
||||
struct lm78_data *isa = pdev ? platform_get_drvdata(pdev) : NULL;
|
||||
struct lm78_data *isa = lm78_data_if_isa();
|
||||
const char *client_name;
|
||||
struct i2c_adapter *adapter = client->adapter;
|
||||
int address = client->addr;
|
||||
|
@ -663,76 +650,24 @@ static int lm78_i2c_remove(struct i2c_client *client)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int __devinit lm78_isa_probe(struct platform_device *pdev)
|
||||
{
|
||||
int err;
|
||||
struct lm78_data *data;
|
||||
struct resource *res;
|
||||
static const struct i2c_device_id lm78_i2c_id[] = {
|
||||
{ "lm78", lm78 },
|
||||
{ "lm79", lm79 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, lm78_i2c_id);
|
||||
|
||||
/* Reserve the ISA region */
|
||||
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
|
||||
if (!request_region(res->start + LM78_ADDR_REG_OFFSET, 2, "lm78")) {
|
||||
err = -EBUSY;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (!(data = kzalloc(sizeof(struct lm78_data), GFP_KERNEL))) {
|
||||
err = -ENOMEM;
|
||||
goto exit_release_region;
|
||||
}
|
||||
mutex_init(&data->lock);
|
||||
data->isa_addr = res->start;
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
if (lm78_read_value(data, LM78_REG_CHIPID) & 0x80) {
|
||||
data->type = lm79;
|
||||
data->name = "lm79";
|
||||
} else {
|
||||
data->type = lm78;
|
||||
data->name = "lm78";
|
||||
}
|
||||
|
||||
/* Initialize the LM78 chip */
|
||||
lm78_init_device(data);
|
||||
|
||||
/* Register sysfs hooks */
|
||||
if ((err = sysfs_create_group(&pdev->dev.kobj, &lm78_group))
|
||||
|| (err = device_create_file(&pdev->dev, &dev_attr_name)))
|
||||
goto exit_remove_files;
|
||||
|
||||
data->hwmon_dev = hwmon_device_register(&pdev->dev);
|
||||
if (IS_ERR(data->hwmon_dev)) {
|
||||
err = PTR_ERR(data->hwmon_dev);
|
||||
goto exit_remove_files;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
exit_remove_files:
|
||||
sysfs_remove_group(&pdev->dev.kobj, &lm78_group);
|
||||
device_remove_file(&pdev->dev, &dev_attr_name);
|
||||
kfree(data);
|
||||
exit_release_region:
|
||||
release_region(res->start + LM78_ADDR_REG_OFFSET, 2);
|
||||
exit:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int __devexit lm78_isa_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct lm78_data *data = platform_get_drvdata(pdev);
|
||||
struct resource *res;
|
||||
|
||||
hwmon_device_unregister(data->hwmon_dev);
|
||||
sysfs_remove_group(&pdev->dev.kobj, &lm78_group);
|
||||
device_remove_file(&pdev->dev, &dev_attr_name);
|
||||
kfree(data);
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
|
||||
release_region(res->start + LM78_ADDR_REG_OFFSET, 2);
|
||||
|
||||
return 0;
|
||||
}
|
||||
static struct i2c_driver lm78_driver = {
|
||||
.class = I2C_CLASS_HWMON,
|
||||
.driver = {
|
||||
.name = "lm78",
|
||||
},
|
||||
.probe = lm78_i2c_probe,
|
||||
.remove = lm78_i2c_remove,
|
||||
.id_table = lm78_i2c_id,
|
||||
.detect = lm78_i2c_detect,
|
||||
.address_list = normal_i2c,
|
||||
};
|
||||
|
||||
/* The SMBus locks itself, but ISA access must be locked explicitly!
|
||||
We don't want to lock the whole ISA bus, so we lock each client
|
||||
|
@ -743,6 +678,7 @@ static int lm78_read_value(struct lm78_data *data, u8 reg)
|
|||
{
|
||||
struct i2c_client *client = data->client;
|
||||
|
||||
#ifdef CONFIG_ISA
|
||||
if (!client) { /* ISA device */
|
||||
int res;
|
||||
mutex_lock(&data->lock);
|
||||
|
@ -751,6 +687,7 @@ static int lm78_read_value(struct lm78_data *data, u8 reg)
|
|||
mutex_unlock(&data->lock);
|
||||
return res;
|
||||
} else
|
||||
#endif
|
||||
return i2c_smbus_read_byte_data(client, reg);
|
||||
}
|
||||
|
||||
|
@ -765,6 +702,7 @@ static int lm78_write_value(struct lm78_data *data, u8 reg, u8 value)
|
|||
{
|
||||
struct i2c_client *client = data->client;
|
||||
|
||||
#ifdef CONFIG_ISA
|
||||
if (!client) { /* ISA device */
|
||||
mutex_lock(&data->lock);
|
||||
outb_p(reg, data->isa_addr + LM78_ADDR_REG_OFFSET);
|
||||
|
@ -772,6 +710,7 @@ static int lm78_write_value(struct lm78_data *data, u8 reg, u8 value)
|
|||
mutex_unlock(&data->lock);
|
||||
return 0;
|
||||
} else
|
||||
#endif
|
||||
return i2c_smbus_write_byte_data(client, reg, value);
|
||||
}
|
||||
|
||||
|
@ -849,6 +788,88 @@ static struct lm78_data *lm78_update_device(struct device *dev)
|
|||
return data;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ISA
|
||||
static int __devinit lm78_isa_probe(struct platform_device *pdev)
|
||||
{
|
||||
int err;
|
||||
struct lm78_data *data;
|
||||
struct resource *res;
|
||||
|
||||
/* Reserve the ISA region */
|
||||
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
|
||||
if (!request_region(res->start + LM78_ADDR_REG_OFFSET, 2, "lm78")) {
|
||||
err = -EBUSY;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
data = kzalloc(sizeof(struct lm78_data), GFP_KERNEL);
|
||||
if (!data) {
|
||||
err = -ENOMEM;
|
||||
goto exit_release_region;
|
||||
}
|
||||
mutex_init(&data->lock);
|
||||
data->isa_addr = res->start;
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
if (lm78_read_value(data, LM78_REG_CHIPID) & 0x80) {
|
||||
data->type = lm79;
|
||||
data->name = "lm79";
|
||||
} else {
|
||||
data->type = lm78;
|
||||
data->name = "lm78";
|
||||
}
|
||||
|
||||
/* Initialize the LM78 chip */
|
||||
lm78_init_device(data);
|
||||
|
||||
/* Register sysfs hooks */
|
||||
if ((err = sysfs_create_group(&pdev->dev.kobj, &lm78_group))
|
||||
|| (err = device_create_file(&pdev->dev, &dev_attr_name)))
|
||||
goto exit_remove_files;
|
||||
|
||||
data->hwmon_dev = hwmon_device_register(&pdev->dev);
|
||||
if (IS_ERR(data->hwmon_dev)) {
|
||||
err = PTR_ERR(data->hwmon_dev);
|
||||
goto exit_remove_files;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
exit_remove_files:
|
||||
sysfs_remove_group(&pdev->dev.kobj, &lm78_group);
|
||||
device_remove_file(&pdev->dev, &dev_attr_name);
|
||||
kfree(data);
|
||||
exit_release_region:
|
||||
release_region(res->start + LM78_ADDR_REG_OFFSET, 2);
|
||||
exit:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int __devexit lm78_isa_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct lm78_data *data = platform_get_drvdata(pdev);
|
||||
struct resource *res;
|
||||
|
||||
hwmon_device_unregister(data->hwmon_dev);
|
||||
sysfs_remove_group(&pdev->dev.kobj, &lm78_group);
|
||||
device_remove_file(&pdev->dev, &dev_attr_name);
|
||||
kfree(data);
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
|
||||
release_region(res->start + LM78_ADDR_REG_OFFSET, 2);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver lm78_isa_driver = {
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "lm78",
|
||||
},
|
||||
.probe = lm78_isa_probe,
|
||||
.remove = __devexit_p(lm78_isa_remove),
|
||||
};
|
||||
|
||||
/* return 1 if a supported chip is found, 0 otherwise */
|
||||
static int __init lm78_isa_found(unsigned short address)
|
||||
{
|
||||
|
@ -969,12 +990,10 @@ static int __init lm78_isa_device_add(unsigned short address)
|
|||
return err;
|
||||
}
|
||||
|
||||
static int __init sm_lm78_init(void)
|
||||
static int __init lm78_isa_register(void)
|
||||
{
|
||||
int res;
|
||||
|
||||
/* We register the ISA device first, so that we can skip the
|
||||
* registration of an I2C interface to the same device. */
|
||||
if (lm78_isa_found(isa_address)) {
|
||||
res = platform_driver_register(&lm78_isa_driver);
|
||||
if (res)
|
||||
|
@ -986,6 +1005,43 @@ static int __init sm_lm78_init(void)
|
|||
goto exit_unreg_isa_driver;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
exit_unreg_isa_driver:
|
||||
platform_driver_unregister(&lm78_isa_driver);
|
||||
exit:
|
||||
return res;
|
||||
}
|
||||
|
||||
static void lm78_isa_unregister(void)
|
||||
{
|
||||
if (pdev) {
|
||||
platform_device_unregister(pdev);
|
||||
platform_driver_unregister(&lm78_isa_driver);
|
||||
}
|
||||
}
|
||||
#else /* !CONFIG_ISA */
|
||||
|
||||
static int __init lm78_isa_register(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void lm78_isa_unregister(void)
|
||||
{
|
||||
}
|
||||
#endif /* CONFIG_ISA */
|
||||
|
||||
static int __init sm_lm78_init(void)
|
||||
{
|
||||
int res;
|
||||
|
||||
/* We register the ISA device first, so that we can skip the
|
||||
* registration of an I2C interface to the same device. */
|
||||
res = lm78_isa_register();
|
||||
if (res)
|
||||
goto exit;
|
||||
|
||||
res = i2c_add_driver(&lm78_driver);
|
||||
if (res)
|
||||
goto exit_unreg_isa_device;
|
||||
|
@ -993,25 +1049,18 @@ static int __init sm_lm78_init(void)
|
|||
return 0;
|
||||
|
||||
exit_unreg_isa_device:
|
||||
platform_device_unregister(pdev);
|
||||
exit_unreg_isa_driver:
|
||||
platform_driver_unregister(&lm78_isa_driver);
|
||||
lm78_isa_unregister();
|
||||
exit:
|
||||
return res;
|
||||
}
|
||||
|
||||
static void __exit sm_lm78_exit(void)
|
||||
{
|
||||
if (pdev) {
|
||||
platform_device_unregister(pdev);
|
||||
platform_driver_unregister(&lm78_isa_driver);
|
||||
}
|
||||
lm78_isa_unregister();
|
||||
i2c_del_driver(&lm78_driver);
|
||||
}
|
||||
|
||||
|
||||
|
||||
MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl>");
|
||||
MODULE_AUTHOR("Frodo Looijaard, Jean Delvare <khali@linux-fr.org>");
|
||||
MODULE_DESCRIPTION("LM78/LM79 driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
|
|
|
@ -38,8 +38,8 @@ struct max1111_data {
|
|||
struct device *hwmon_dev;
|
||||
struct spi_message msg;
|
||||
struct spi_transfer xfer[2];
|
||||
uint8_t *tx_buf;
|
||||
uint8_t *rx_buf;
|
||||
uint8_t tx_buf[MAX1111_TX_BUF_SIZE];
|
||||
uint8_t rx_buf[MAX1111_RX_BUF_SIZE];
|
||||
struct mutex drvdata_lock;
|
||||
/* protect msg, xfer and buffers from multiple access */
|
||||
};
|
||||
|
@ -131,33 +131,23 @@ static const struct attribute_group max1111_attr_group = {
|
|||
.attrs = max1111_attributes,
|
||||
};
|
||||
|
||||
static int setup_transfer(struct max1111_data *data)
|
||||
static int __devinit setup_transfer(struct max1111_data *data)
|
||||
{
|
||||
struct spi_message *m;
|
||||
struct spi_transfer *x;
|
||||
|
||||
data->tx_buf = kmalloc(MAX1111_TX_BUF_SIZE, GFP_KERNEL);
|
||||
if (!data->tx_buf)
|
||||
return -ENOMEM;
|
||||
|
||||
data->rx_buf = kmalloc(MAX1111_RX_BUF_SIZE, GFP_KERNEL);
|
||||
if (!data->rx_buf) {
|
||||
kfree(data->tx_buf);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
m = &data->msg;
|
||||
x = &data->xfer[0];
|
||||
|
||||
spi_message_init(m);
|
||||
|
||||
x->tx_buf = &data->tx_buf[0];
|
||||
x->len = 1;
|
||||
x->len = MAX1111_TX_BUF_SIZE;
|
||||
spi_message_add_tail(x, m);
|
||||
|
||||
x++;
|
||||
x->rx_buf = &data->rx_buf[0];
|
||||
x->len = 2;
|
||||
x->len = MAX1111_RX_BUF_SIZE;
|
||||
spi_message_add_tail(x, m);
|
||||
|
||||
return 0;
|
||||
|
@ -192,7 +182,7 @@ static int __devinit max1111_probe(struct spi_device *spi)
|
|||
err = sysfs_create_group(&spi->dev.kobj, &max1111_attr_group);
|
||||
if (err) {
|
||||
dev_err(&spi->dev, "failed to create attribute group\n");
|
||||
goto err_free_all;
|
||||
goto err_free_data;
|
||||
}
|
||||
|
||||
data->hwmon_dev = hwmon_device_register(&spi->dev);
|
||||
|
@ -209,9 +199,6 @@ static int __devinit max1111_probe(struct spi_device *spi)
|
|||
|
||||
err_remove:
|
||||
sysfs_remove_group(&spi->dev.kobj, &max1111_attr_group);
|
||||
err_free_all:
|
||||
kfree(data->rx_buf);
|
||||
kfree(data->tx_buf);
|
||||
err_free_data:
|
||||
kfree(data);
|
||||
return err;
|
||||
|
@ -224,8 +211,6 @@ static int __devexit max1111_remove(struct spi_device *spi)
|
|||
hwmon_device_unregister(data->hwmon_dev);
|
||||
sysfs_remove_group(&spi->dev.kobj, &max1111_attr_group);
|
||||
mutex_destroy(&data->drvdata_lock);
|
||||
kfree(data->rx_buf);
|
||||
kfree(data->tx_buf);
|
||||
kfree(data);
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -28,33 +28,15 @@
|
|||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/delay.h>
|
||||
#include "sch56xx-common.h"
|
||||
|
||||
#define DRVNAME "sch5627"
|
||||
#define DEVNAME DRVNAME /* We only support one model */
|
||||
|
||||
#define SIO_SCH5627_EM_LD 0x0C /* Embedded Microcontroller LD */
|
||||
#define SIO_UNLOCK_KEY 0x55 /* Key to enable Super-I/O */
|
||||
#define SIO_LOCK_KEY 0xAA /* Key to disable Super-I/O */
|
||||
|
||||
#define SIO_REG_LDSEL 0x07 /* Logical device select */
|
||||
#define SIO_REG_DEVID 0x20 /* Device ID */
|
||||
#define SIO_REG_ENABLE 0x30 /* Logical device enable */
|
||||
#define SIO_REG_ADDR 0x66 /* Logical device address (2 bytes) */
|
||||
|
||||
#define SIO_SCH5627_ID 0xC6 /* Chipset ID */
|
||||
|
||||
#define REGION_LENGTH 9
|
||||
|
||||
#define SCH5627_HWMON_ID 0xa5
|
||||
#define SCH5627_COMPANY_ID 0x5c
|
||||
#define SCH5627_PRIMARY_ID 0xa0
|
||||
|
||||
#define SCH5627_CMD_READ 0x02
|
||||
#define SCH5627_CMD_WRITE 0x03
|
||||
|
||||
#define SCH5627_REG_BUILD_CODE 0x39
|
||||
#define SCH5627_REG_BUILD_ID 0x3a
|
||||
#define SCH5627_REG_HWMON_ID 0x3c
|
||||
|
@ -111,182 +93,6 @@ struct sch5627_data {
|
|||
u16 in[SCH5627_NO_IN];
|
||||
};
|
||||
|
||||
static struct platform_device *sch5627_pdev;
|
||||
|
||||
/* Super I/O functions */
|
||||
static inline int superio_inb(int base, int reg)
|
||||
{
|
||||
outb(reg, base);
|
||||
return inb(base + 1);
|
||||
}
|
||||
|
||||
static inline int superio_enter(int base)
|
||||
{
|
||||
/* Don't step on other drivers' I/O space by accident */
|
||||
if (!request_muxed_region(base, 2, DRVNAME)) {
|
||||
pr_err("I/O address 0x%04x already in use\n", base);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
outb(SIO_UNLOCK_KEY, base);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void superio_select(int base, int ld)
|
||||
{
|
||||
outb(SIO_REG_LDSEL, base);
|
||||
outb(ld, base + 1);
|
||||
}
|
||||
|
||||
static inline void superio_exit(int base)
|
||||
{
|
||||
outb(SIO_LOCK_KEY, base);
|
||||
release_region(base, 2);
|
||||
}
|
||||
|
||||
static int sch5627_send_cmd(struct sch5627_data *data, u8 cmd, u16 reg, u8 v)
|
||||
{
|
||||
u8 val;
|
||||
int i;
|
||||
/*
|
||||
* According to SMSC for the commands we use the maximum time for
|
||||
* the EM to respond is 15 ms, but testing shows in practice it
|
||||
* responds within 15-32 reads, so we first busy poll, and if
|
||||
* that fails sleep a bit and try again until we are way past
|
||||
* the 15 ms maximum response time.
|
||||
*/
|
||||
const int max_busy_polls = 64;
|
||||
const int max_lazy_polls = 32;
|
||||
|
||||
/* (Optional) Write-Clear the EC to Host Mailbox Register */
|
||||
val = inb(data->addr + 1);
|
||||
outb(val, data->addr + 1);
|
||||
|
||||
/* Set Mailbox Address Pointer to first location in Region 1 */
|
||||
outb(0x00, data->addr + 2);
|
||||
outb(0x80, data->addr + 3);
|
||||
|
||||
/* Write Request Packet Header */
|
||||
outb(cmd, data->addr + 4); /* VREG Access Type read:0x02 write:0x03 */
|
||||
outb(0x01, data->addr + 5); /* # of Entries: 1 Byte (8-bit) */
|
||||
outb(0x04, data->addr + 2); /* Mailbox AP to first data entry loc. */
|
||||
|
||||
/* Write Value field */
|
||||
if (cmd == SCH5627_CMD_WRITE)
|
||||
outb(v, data->addr + 4);
|
||||
|
||||
/* Write Address field */
|
||||
outb(reg & 0xff, data->addr + 6);
|
||||
outb(reg >> 8, data->addr + 7);
|
||||
|
||||
/* Execute the Random Access Command */
|
||||
outb(0x01, data->addr); /* Write 01h to the Host-to-EC register */
|
||||
|
||||
/* EM Interface Polling "Algorithm" */
|
||||
for (i = 0; i < max_busy_polls + max_lazy_polls; i++) {
|
||||
if (i >= max_busy_polls)
|
||||
msleep(1);
|
||||
/* Read Interrupt source Register */
|
||||
val = inb(data->addr + 8);
|
||||
/* Write Clear the interrupt source bits */
|
||||
if (val)
|
||||
outb(val, data->addr + 8);
|
||||
/* Command Completed ? */
|
||||
if (val & 0x01)
|
||||
break;
|
||||
}
|
||||
if (i == max_busy_polls + max_lazy_polls) {
|
||||
pr_err("Max retries exceeded reading virtual "
|
||||
"register 0x%04hx (%d)\n", reg, 1);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/*
|
||||
* According to SMSC we may need to retry this, but sofar I've always
|
||||
* seen this succeed in 1 try.
|
||||
*/
|
||||
for (i = 0; i < max_busy_polls; i++) {
|
||||
/* Read EC-to-Host Register */
|
||||
val = inb(data->addr + 1);
|
||||
/* Command Completed ? */
|
||||
if (val == 0x01)
|
||||
break;
|
||||
|
||||
if (i == 0)
|
||||
pr_warn("EC reports: 0x%02x reading virtual register "
|
||||
"0x%04hx\n", (unsigned int)val, reg);
|
||||
}
|
||||
if (i == max_busy_polls) {
|
||||
pr_err("Max retries exceeded reading virtual "
|
||||
"register 0x%04hx (%d)\n", reg, 2);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/*
|
||||
* According to the SMSC app note we should now do:
|
||||
*
|
||||
* Set Mailbox Address Pointer to first location in Region 1 *
|
||||
* outb(0x00, data->addr + 2);
|
||||
* outb(0x80, data->addr + 3);
|
||||
*
|
||||
* But if we do that things don't work, so let's not.
|
||||
*/
|
||||
|
||||
/* Read Value field */
|
||||
if (cmd == SCH5627_CMD_READ)
|
||||
return inb(data->addr + 4);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sch5627_read_virtual_reg(struct sch5627_data *data, u16 reg)
|
||||
{
|
||||
return sch5627_send_cmd(data, SCH5627_CMD_READ, reg, 0);
|
||||
}
|
||||
|
||||
static int sch5627_write_virtual_reg(struct sch5627_data *data,
|
||||
u16 reg, u8 val)
|
||||
{
|
||||
return sch5627_send_cmd(data, SCH5627_CMD_WRITE, reg, val);
|
||||
}
|
||||
|
||||
static int sch5627_read_virtual_reg16(struct sch5627_data *data, u16 reg)
|
||||
{
|
||||
int lsb, msb;
|
||||
|
||||
/* Read LSB first, this will cause the matching MSB to be latched */
|
||||
lsb = sch5627_read_virtual_reg(data, reg);
|
||||
if (lsb < 0)
|
||||
return lsb;
|
||||
|
||||
msb = sch5627_read_virtual_reg(data, reg + 1);
|
||||
if (msb < 0)
|
||||
return msb;
|
||||
|
||||
return lsb | (msb << 8);
|
||||
}
|
||||
|
||||
static int sch5627_read_virtual_reg12(struct sch5627_data *data, u16 msb_reg,
|
||||
u16 lsn_reg, int high_nibble)
|
||||
{
|
||||
int msb, lsn;
|
||||
|
||||
/* Read MSB first, this will cause the matching LSN to be latched */
|
||||
msb = sch5627_read_virtual_reg(data, msb_reg);
|
||||
if (msb < 0)
|
||||
return msb;
|
||||
|
||||
lsn = sch5627_read_virtual_reg(data, lsn_reg);
|
||||
if (lsn < 0)
|
||||
return lsn;
|
||||
|
||||
if (high_nibble)
|
||||
return (msb << 4) | (lsn >> 4);
|
||||
else
|
||||
return (msb << 4) | (lsn & 0x0f);
|
||||
}
|
||||
|
||||
static struct sch5627_data *sch5627_update_device(struct device *dev)
|
||||
{
|
||||
struct sch5627_data *data = dev_get_drvdata(dev);
|
||||
|
@ -297,7 +103,7 @@ static struct sch5627_data *sch5627_update_device(struct device *dev)
|
|||
|
||||
/* Trigger a Vbat voltage measurement every 5 minutes */
|
||||
if (time_after(jiffies, data->last_battery + 300 * HZ)) {
|
||||
sch5627_write_virtual_reg(data, SCH5627_REG_CTRL,
|
||||
sch56xx_write_virtual_reg(data->addr, SCH5627_REG_CTRL,
|
||||
data->control | 0x10);
|
||||
data->last_battery = jiffies;
|
||||
}
|
||||
|
@ -305,7 +111,7 @@ static struct sch5627_data *sch5627_update_device(struct device *dev)
|
|||
/* Cache the values for 1 second */
|
||||
if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
|
||||
for (i = 0; i < SCH5627_NO_TEMPS; i++) {
|
||||
val = sch5627_read_virtual_reg12(data,
|
||||
val = sch56xx_read_virtual_reg12(data->addr,
|
||||
SCH5627_REG_TEMP_MSB[i],
|
||||
SCH5627_REG_TEMP_LSN[i],
|
||||
SCH5627_REG_TEMP_HIGH_NIBBLE[i]);
|
||||
|
@ -317,7 +123,7 @@ static struct sch5627_data *sch5627_update_device(struct device *dev)
|
|||
}
|
||||
|
||||
for (i = 0; i < SCH5627_NO_FANS; i++) {
|
||||
val = sch5627_read_virtual_reg16(data,
|
||||
val = sch56xx_read_virtual_reg16(data->addr,
|
||||
SCH5627_REG_FAN[i]);
|
||||
if (unlikely(val < 0)) {
|
||||
ret = ERR_PTR(val);
|
||||
|
@ -327,7 +133,7 @@ static struct sch5627_data *sch5627_update_device(struct device *dev)
|
|||
}
|
||||
|
||||
for (i = 0; i < SCH5627_NO_IN; i++) {
|
||||
val = sch5627_read_virtual_reg12(data,
|
||||
val = sch56xx_read_virtual_reg12(data->addr,
|
||||
SCH5627_REG_IN_MSB[i],
|
||||
SCH5627_REG_IN_LSN[i],
|
||||
SCH5627_REG_IN_HIGH_NIBBLE[i]);
|
||||
|
@ -355,18 +161,21 @@ static int __devinit sch5627_read_limits(struct sch5627_data *data)
|
|||
* Note what SMSC calls ABS, is what lm_sensors calls max
|
||||
* (aka high), and HIGH is what lm_sensors calls crit.
|
||||
*/
|
||||
val = sch5627_read_virtual_reg(data, SCH5627_REG_TEMP_ABS[i]);
|
||||
val = sch56xx_read_virtual_reg(data->addr,
|
||||
SCH5627_REG_TEMP_ABS[i]);
|
||||
if (val < 0)
|
||||
return val;
|
||||
data->temp_max[i] = val;
|
||||
|
||||
val = sch5627_read_virtual_reg(data, SCH5627_REG_TEMP_HIGH[i]);
|
||||
val = sch56xx_read_virtual_reg(data->addr,
|
||||
SCH5627_REG_TEMP_HIGH[i]);
|
||||
if (val < 0)
|
||||
return val;
|
||||
data->temp_crit[i] = val;
|
||||
}
|
||||
for (i = 0; i < SCH5627_NO_FANS; i++) {
|
||||
val = sch5627_read_virtual_reg16(data, SCH5627_REG_FAN_MIN[i]);
|
||||
val = sch56xx_read_virtual_reg16(data->addr,
|
||||
SCH5627_REG_FAN_MIN[i]);
|
||||
if (val < 0)
|
||||
return val;
|
||||
data->fan_min[i] = val;
|
||||
|
@ -667,7 +476,7 @@ static int __devinit sch5627_probe(struct platform_device *pdev)
|
|||
mutex_init(&data->update_lock);
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
val = sch5627_read_virtual_reg(data, SCH5627_REG_HWMON_ID);
|
||||
val = sch56xx_read_virtual_reg(data->addr, SCH5627_REG_HWMON_ID);
|
||||
if (val < 0) {
|
||||
err = val;
|
||||
goto error;
|
||||
|
@ -679,7 +488,7 @@ static int __devinit sch5627_probe(struct platform_device *pdev)
|
|||
goto error;
|
||||
}
|
||||
|
||||
val = sch5627_read_virtual_reg(data, SCH5627_REG_COMPANY_ID);
|
||||
val = sch56xx_read_virtual_reg(data->addr, SCH5627_REG_COMPANY_ID);
|
||||
if (val < 0) {
|
||||
err = val;
|
||||
goto error;
|
||||
|
@ -691,7 +500,7 @@ static int __devinit sch5627_probe(struct platform_device *pdev)
|
|||
goto error;
|
||||
}
|
||||
|
||||
val = sch5627_read_virtual_reg(data, SCH5627_REG_PRIMARY_ID);
|
||||
val = sch56xx_read_virtual_reg(data->addr, SCH5627_REG_PRIMARY_ID);
|
||||
if (val < 0) {
|
||||
err = val;
|
||||
goto error;
|
||||
|
@ -703,25 +512,28 @@ static int __devinit sch5627_probe(struct platform_device *pdev)
|
|||
goto error;
|
||||
}
|
||||
|
||||
build_code = sch5627_read_virtual_reg(data, SCH5627_REG_BUILD_CODE);
|
||||
build_code = sch56xx_read_virtual_reg(data->addr,
|
||||
SCH5627_REG_BUILD_CODE);
|
||||
if (build_code < 0) {
|
||||
err = build_code;
|
||||
goto error;
|
||||
}
|
||||
|
||||
build_id = sch5627_read_virtual_reg16(data, SCH5627_REG_BUILD_ID);
|
||||
build_id = sch56xx_read_virtual_reg16(data->addr,
|
||||
SCH5627_REG_BUILD_ID);
|
||||
if (build_id < 0) {
|
||||
err = build_id;
|
||||
goto error;
|
||||
}
|
||||
|
||||
hwmon_rev = sch5627_read_virtual_reg(data, SCH5627_REG_HWMON_REV);
|
||||
hwmon_rev = sch56xx_read_virtual_reg(data->addr,
|
||||
SCH5627_REG_HWMON_REV);
|
||||
if (hwmon_rev < 0) {
|
||||
err = hwmon_rev;
|
||||
goto error;
|
||||
}
|
||||
|
||||
val = sch5627_read_virtual_reg(data, SCH5627_REG_CTRL);
|
||||
val = sch56xx_read_virtual_reg(data->addr, SCH5627_REG_CTRL);
|
||||
if (val < 0) {
|
||||
err = val;
|
||||
goto error;
|
||||
|
@ -734,7 +546,7 @@ static int __devinit sch5627_probe(struct platform_device *pdev)
|
|||
}
|
||||
/* Trigger a Vbat voltage measurement, so that we get a valid reading
|
||||
the first time we read Vbat */
|
||||
sch5627_write_virtual_reg(data, SCH5627_REG_CTRL,
|
||||
sch56xx_write_virtual_reg(data->addr, SCH5627_REG_CTRL,
|
||||
data->control | 0x10);
|
||||
data->last_battery = jiffies;
|
||||
|
||||
|
@ -746,6 +558,7 @@ static int __devinit sch5627_probe(struct platform_device *pdev)
|
|||
if (err)
|
||||
goto error;
|
||||
|
||||
pr_info("found %s chip at %#hx\n", DEVNAME, data->addr);
|
||||
pr_info("firmware build: code 0x%02X, id 0x%04X, hwmon: rev 0x%02X\n",
|
||||
build_code, build_id, hwmon_rev);
|
||||
|
||||
|
@ -768,85 +581,6 @@ error:
|
|||
return err;
|
||||
}
|
||||
|
||||
static int __init sch5627_find(int sioaddr, unsigned short *address)
|
||||
{
|
||||
u8 devid;
|
||||
int err = superio_enter(sioaddr);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
devid = superio_inb(sioaddr, SIO_REG_DEVID);
|
||||
if (devid != SIO_SCH5627_ID) {
|
||||
pr_debug("Unsupported device id: 0x%02x\n",
|
||||
(unsigned int)devid);
|
||||
err = -ENODEV;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
superio_select(sioaddr, SIO_SCH5627_EM_LD);
|
||||
|
||||
if (!(superio_inb(sioaddr, SIO_REG_ENABLE) & 0x01)) {
|
||||
pr_warn("Device not activated\n");
|
||||
err = -ENODEV;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/*
|
||||
* Warning the order of the low / high byte is the other way around
|
||||
* as on most other superio devices!!
|
||||
*/
|
||||
*address = superio_inb(sioaddr, SIO_REG_ADDR) |
|
||||
superio_inb(sioaddr, SIO_REG_ADDR + 1) << 8;
|
||||
if (*address == 0) {
|
||||
pr_warn("Base address not set\n");
|
||||
err = -ENODEV;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
pr_info("Found %s chip at %#hx\n", DEVNAME, *address);
|
||||
exit:
|
||||
superio_exit(sioaddr);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int __init sch5627_device_add(unsigned short address)
|
||||
{
|
||||
struct resource res = {
|
||||
.start = address,
|
||||
.end = address + REGION_LENGTH - 1,
|
||||
.flags = IORESOURCE_IO,
|
||||
};
|
||||
int err;
|
||||
|
||||
sch5627_pdev = platform_device_alloc(DRVNAME, address);
|
||||
if (!sch5627_pdev)
|
||||
return -ENOMEM;
|
||||
|
||||
res.name = sch5627_pdev->name;
|
||||
err = acpi_check_resource_conflict(&res);
|
||||
if (err)
|
||||
goto exit_device_put;
|
||||
|
||||
err = platform_device_add_resources(sch5627_pdev, &res, 1);
|
||||
if (err) {
|
||||
pr_err("Device resource addition failed\n");
|
||||
goto exit_device_put;
|
||||
}
|
||||
|
||||
err = platform_device_add(sch5627_pdev);
|
||||
if (err) {
|
||||
pr_err("Device addition failed\n");
|
||||
goto exit_device_put;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
exit_device_put:
|
||||
platform_device_put(sch5627_pdev);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static struct platform_driver sch5627_driver = {
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
|
@ -858,31 +592,11 @@ static struct platform_driver sch5627_driver = {
|
|||
|
||||
static int __init sch5627_init(void)
|
||||
{
|
||||
int err = -ENODEV;
|
||||
unsigned short address;
|
||||
|
||||
if (sch5627_find(0x4e, &address) && sch5627_find(0x2e, &address))
|
||||
goto exit;
|
||||
|
||||
err = platform_driver_register(&sch5627_driver);
|
||||
if (err)
|
||||
goto exit;
|
||||
|
||||
err = sch5627_device_add(address);
|
||||
if (err)
|
||||
goto exit_driver;
|
||||
|
||||
return 0;
|
||||
|
||||
exit_driver:
|
||||
platform_driver_unregister(&sch5627_driver);
|
||||
exit:
|
||||
return err;
|
||||
return platform_driver_register(&sch5627_driver);
|
||||
}
|
||||
|
||||
static void __exit sch5627_exit(void)
|
||||
{
|
||||
platform_device_unregister(sch5627_pdev);
|
||||
platform_driver_unregister(&sch5627_driver);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,539 @@
|
|||
/***************************************************************************
|
||||
* Copyright (C) 2011 Hans de Goede <hdegoede@redhat.com> *
|
||||
* *
|
||||
* 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. *
|
||||
***************************************************************************/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/mutex.h>
|
||||
#include "sch56xx-common.h"
|
||||
|
||||
#define DRVNAME "sch5636"
|
||||
#define DEVNAME "theseus" /* We only support one model for now */
|
||||
|
||||
#define SCH5636_REG_FUJITSU_ID 0x780
|
||||
#define SCH5636_REG_FUJITSU_REV 0x783
|
||||
|
||||
#define SCH5636_NO_INS 5
|
||||
#define SCH5636_NO_TEMPS 16
|
||||
#define SCH5636_NO_FANS 8
|
||||
|
||||
static const u16 SCH5636_REG_IN_VAL[SCH5636_NO_INS] = {
|
||||
0x22, 0x23, 0x24, 0x25, 0x189 };
|
||||
static const u16 SCH5636_REG_IN_FACTORS[SCH5636_NO_INS] = {
|
||||
4400, 1500, 4000, 4400, 16000 };
|
||||
static const char * const SCH5636_IN_LABELS[SCH5636_NO_INS] = {
|
||||
"3.3V", "VREF", "VBAT", "3.3AUX", "12V" };
|
||||
|
||||
static const u16 SCH5636_REG_TEMP_VAL[SCH5636_NO_TEMPS] = {
|
||||
0x2B, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x180, 0x181,
|
||||
0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C };
|
||||
#define SCH5636_REG_TEMP_CTRL(i) (0x790 + (i))
|
||||
#define SCH5636_TEMP_WORKING 0x01
|
||||
#define SCH5636_TEMP_ALARM 0x02
|
||||
#define SCH5636_TEMP_DEACTIVATED 0x80
|
||||
|
||||
static const u16 SCH5636_REG_FAN_VAL[SCH5636_NO_FANS] = {
|
||||
0x2C, 0x2E, 0x30, 0x32, 0x62, 0x64, 0x66, 0x68 };
|
||||
#define SCH5636_REG_FAN_CTRL(i) (0x880 + (i))
|
||||
/* FAULT in datasheet, but acts as an alarm */
|
||||
#define SCH5636_FAN_ALARM 0x04
|
||||
#define SCH5636_FAN_NOT_PRESENT 0x08
|
||||
#define SCH5636_FAN_DEACTIVATED 0x80
|
||||
|
||||
|
||||
struct sch5636_data {
|
||||
unsigned short addr;
|
||||
struct device *hwmon_dev;
|
||||
|
||||
struct mutex update_lock;
|
||||
char valid; /* !=0 if following fields are valid */
|
||||
unsigned long last_updated; /* In jiffies */
|
||||
u8 in[SCH5636_NO_INS];
|
||||
u8 temp_val[SCH5636_NO_TEMPS];
|
||||
u8 temp_ctrl[SCH5636_NO_TEMPS];
|
||||
u16 fan_val[SCH5636_NO_FANS];
|
||||
u8 fan_ctrl[SCH5636_NO_FANS];
|
||||
};
|
||||
|
||||
static struct sch5636_data *sch5636_update_device(struct device *dev)
|
||||
{
|
||||
struct sch5636_data *data = dev_get_drvdata(dev);
|
||||
struct sch5636_data *ret = data;
|
||||
int i, val;
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
|
||||
/* Cache the values for 1 second */
|
||||
if (data->valid && !time_after(jiffies, data->last_updated + HZ))
|
||||
goto abort;
|
||||
|
||||
for (i = 0; i < SCH5636_NO_INS; i++) {
|
||||
val = sch56xx_read_virtual_reg(data->addr,
|
||||
SCH5636_REG_IN_VAL[i]);
|
||||
if (unlikely(val < 0)) {
|
||||
ret = ERR_PTR(val);
|
||||
goto abort;
|
||||
}
|
||||
data->in[i] = val;
|
||||
}
|
||||
|
||||
for (i = 0; i < SCH5636_NO_TEMPS; i++) {
|
||||
if (data->temp_ctrl[i] & SCH5636_TEMP_DEACTIVATED)
|
||||
continue;
|
||||
|
||||
val = sch56xx_read_virtual_reg(data->addr,
|
||||
SCH5636_REG_TEMP_VAL[i]);
|
||||
if (unlikely(val < 0)) {
|
||||
ret = ERR_PTR(val);
|
||||
goto abort;
|
||||
}
|
||||
data->temp_val[i] = val;
|
||||
|
||||
val = sch56xx_read_virtual_reg(data->addr,
|
||||
SCH5636_REG_TEMP_CTRL(i));
|
||||
if (unlikely(val < 0)) {
|
||||
ret = ERR_PTR(val);
|
||||
goto abort;
|
||||
}
|
||||
data->temp_ctrl[i] = val;
|
||||
/* Alarms need to be explicitly write-cleared */
|
||||
if (val & SCH5636_TEMP_ALARM) {
|
||||
sch56xx_write_virtual_reg(data->addr,
|
||||
SCH5636_REG_TEMP_CTRL(i), val);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < SCH5636_NO_FANS; i++) {
|
||||
if (data->fan_ctrl[i] & SCH5636_FAN_DEACTIVATED)
|
||||
continue;
|
||||
|
||||
val = sch56xx_read_virtual_reg16(data->addr,
|
||||
SCH5636_REG_FAN_VAL[i]);
|
||||
if (unlikely(val < 0)) {
|
||||
ret = ERR_PTR(val);
|
||||
goto abort;
|
||||
}
|
||||
data->fan_val[i] = val;
|
||||
|
||||
val = sch56xx_read_virtual_reg(data->addr,
|
||||
SCH5636_REG_FAN_CTRL(i));
|
||||
if (unlikely(val < 0)) {
|
||||
ret = ERR_PTR(val);
|
||||
goto abort;
|
||||
}
|
||||
data->fan_ctrl[i] = val;
|
||||
/* Alarms need to be explicitly write-cleared */
|
||||
if (val & SCH5636_FAN_ALARM) {
|
||||
sch56xx_write_virtual_reg(data->addr,
|
||||
SCH5636_REG_FAN_CTRL(i), val);
|
||||
}
|
||||
}
|
||||
|
||||
data->last_updated = jiffies;
|
||||
data->valid = 1;
|
||||
abort:
|
||||
mutex_unlock(&data->update_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int reg_to_rpm(u16 reg)
|
||||
{
|
||||
if (reg == 0)
|
||||
return -EIO;
|
||||
if (reg == 0xffff)
|
||||
return 0;
|
||||
|
||||
return 5400540 / reg;
|
||||
}
|
||||
|
||||
static ssize_t show_name(struct device *dev, struct device_attribute *devattr,
|
||||
char *buf)
|
||||
{
|
||||
return snprintf(buf, PAGE_SIZE, "%s\n", DEVNAME);
|
||||
}
|
||||
|
||||
static ssize_t show_in_value(struct device *dev, struct device_attribute
|
||||
*devattr, char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct sch5636_data *data = sch5636_update_device(dev);
|
||||
int val;
|
||||
|
||||
if (IS_ERR(data))
|
||||
return PTR_ERR(data);
|
||||
|
||||
val = DIV_ROUND_CLOSEST(
|
||||
data->in[attr->index] * SCH5636_REG_IN_FACTORS[attr->index],
|
||||
255);
|
||||
return snprintf(buf, PAGE_SIZE, "%d\n", val);
|
||||
}
|
||||
|
||||
static ssize_t show_in_label(struct device *dev, struct device_attribute
|
||||
*devattr, char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%s\n",
|
||||
SCH5636_IN_LABELS[attr->index]);
|
||||
}
|
||||
|
||||
static ssize_t show_temp_value(struct device *dev, struct device_attribute
|
||||
*devattr, char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct sch5636_data *data = sch5636_update_device(dev);
|
||||
int val;
|
||||
|
||||
if (IS_ERR(data))
|
||||
return PTR_ERR(data);
|
||||
|
||||
val = (data->temp_val[attr->index] - 64) * 1000;
|
||||
return snprintf(buf, PAGE_SIZE, "%d\n", val);
|
||||
}
|
||||
|
||||
static ssize_t show_temp_fault(struct device *dev, struct device_attribute
|
||||
*devattr, char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct sch5636_data *data = sch5636_update_device(dev);
|
||||
int val;
|
||||
|
||||
if (IS_ERR(data))
|
||||
return PTR_ERR(data);
|
||||
|
||||
val = (data->temp_ctrl[attr->index] & SCH5636_TEMP_WORKING) ? 0 : 1;
|
||||
return snprintf(buf, PAGE_SIZE, "%d\n", val);
|
||||
}
|
||||
|
||||
static ssize_t show_temp_alarm(struct device *dev, struct device_attribute
|
||||
*devattr, char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct sch5636_data *data = sch5636_update_device(dev);
|
||||
int val;
|
||||
|
||||
if (IS_ERR(data))
|
||||
return PTR_ERR(data);
|
||||
|
||||
val = (data->temp_ctrl[attr->index] & SCH5636_TEMP_ALARM) ? 1 : 0;
|
||||
return snprintf(buf, PAGE_SIZE, "%d\n", val);
|
||||
}
|
||||
|
||||
static ssize_t show_fan_value(struct device *dev, struct device_attribute
|
||||
*devattr, char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct sch5636_data *data = sch5636_update_device(dev);
|
||||
int val;
|
||||
|
||||
if (IS_ERR(data))
|
||||
return PTR_ERR(data);
|
||||
|
||||
val = reg_to_rpm(data->fan_val[attr->index]);
|
||||
if (val < 0)
|
||||
return val;
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%d\n", val);
|
||||
}
|
||||
|
||||
static ssize_t show_fan_fault(struct device *dev, struct device_attribute
|
||||
*devattr, char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct sch5636_data *data = sch5636_update_device(dev);
|
||||
int val;
|
||||
|
||||
if (IS_ERR(data))
|
||||
return PTR_ERR(data);
|
||||
|
||||
val = (data->fan_ctrl[attr->index] & SCH5636_FAN_NOT_PRESENT) ? 1 : 0;
|
||||
return snprintf(buf, PAGE_SIZE, "%d\n", val);
|
||||
}
|
||||
|
||||
static ssize_t show_fan_alarm(struct device *dev, struct device_attribute
|
||||
*devattr, char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct sch5636_data *data = sch5636_update_device(dev);
|
||||
int val;
|
||||
|
||||
if (IS_ERR(data))
|
||||
return PTR_ERR(data);
|
||||
|
||||
val = (data->fan_ctrl[attr->index] & SCH5636_FAN_ALARM) ? 1 : 0;
|
||||
return snprintf(buf, PAGE_SIZE, "%d\n", val);
|
||||
}
|
||||
|
||||
static struct sensor_device_attribute sch5636_attr[] = {
|
||||
SENSOR_ATTR(name, 0444, show_name, NULL, 0),
|
||||
SENSOR_ATTR(in0_input, 0444, show_in_value, NULL, 0),
|
||||
SENSOR_ATTR(in0_label, 0444, show_in_label, NULL, 0),
|
||||
SENSOR_ATTR(in1_input, 0444, show_in_value, NULL, 1),
|
||||
SENSOR_ATTR(in1_label, 0444, show_in_label, NULL, 1),
|
||||
SENSOR_ATTR(in2_input, 0444, show_in_value, NULL, 2),
|
||||
SENSOR_ATTR(in2_label, 0444, show_in_label, NULL, 2),
|
||||
SENSOR_ATTR(in3_input, 0444, show_in_value, NULL, 3),
|
||||
SENSOR_ATTR(in3_label, 0444, show_in_label, NULL, 3),
|
||||
SENSOR_ATTR(in4_input, 0444, show_in_value, NULL, 4),
|
||||
SENSOR_ATTR(in4_label, 0444, show_in_label, NULL, 4),
|
||||
};
|
||||
|
||||
static struct sensor_device_attribute sch5636_temp_attr[] = {
|
||||
SENSOR_ATTR(temp1_input, 0444, show_temp_value, NULL, 0),
|
||||
SENSOR_ATTR(temp1_fault, 0444, show_temp_fault, NULL, 0),
|
||||
SENSOR_ATTR(temp1_alarm, 0444, show_temp_alarm, NULL, 0),
|
||||
SENSOR_ATTR(temp2_input, 0444, show_temp_value, NULL, 1),
|
||||
SENSOR_ATTR(temp2_fault, 0444, show_temp_fault, NULL, 1),
|
||||
SENSOR_ATTR(temp2_alarm, 0444, show_temp_alarm, NULL, 1),
|
||||
SENSOR_ATTR(temp3_input, 0444, show_temp_value, NULL, 2),
|
||||
SENSOR_ATTR(temp3_fault, 0444, show_temp_fault, NULL, 2),
|
||||
SENSOR_ATTR(temp3_alarm, 0444, show_temp_alarm, NULL, 2),
|
||||
SENSOR_ATTR(temp4_input, 0444, show_temp_value, NULL, 3),
|
||||
SENSOR_ATTR(temp4_fault, 0444, show_temp_fault, NULL, 3),
|
||||
SENSOR_ATTR(temp4_alarm, 0444, show_temp_alarm, NULL, 3),
|
||||
SENSOR_ATTR(temp5_input, 0444, show_temp_value, NULL, 4),
|
||||
SENSOR_ATTR(temp5_fault, 0444, show_temp_fault, NULL, 4),
|
||||
SENSOR_ATTR(temp5_alarm, 0444, show_temp_alarm, NULL, 4),
|
||||
SENSOR_ATTR(temp6_input, 0444, show_temp_value, NULL, 5),
|
||||
SENSOR_ATTR(temp6_fault, 0444, show_temp_fault, NULL, 5),
|
||||
SENSOR_ATTR(temp6_alarm, 0444, show_temp_alarm, NULL, 5),
|
||||
SENSOR_ATTR(temp7_input, 0444, show_temp_value, NULL, 6),
|
||||
SENSOR_ATTR(temp7_fault, 0444, show_temp_fault, NULL, 6),
|
||||
SENSOR_ATTR(temp7_alarm, 0444, show_temp_alarm, NULL, 6),
|
||||
SENSOR_ATTR(temp8_input, 0444, show_temp_value, NULL, 7),
|
||||
SENSOR_ATTR(temp8_fault, 0444, show_temp_fault, NULL, 7),
|
||||
SENSOR_ATTR(temp8_alarm, 0444, show_temp_alarm, NULL, 7),
|
||||
SENSOR_ATTR(temp9_input, 0444, show_temp_value, NULL, 8),
|
||||
SENSOR_ATTR(temp9_fault, 0444, show_temp_fault, NULL, 8),
|
||||
SENSOR_ATTR(temp9_alarm, 0444, show_temp_alarm, NULL, 8),
|
||||
SENSOR_ATTR(temp10_input, 0444, show_temp_value, NULL, 9),
|
||||
SENSOR_ATTR(temp10_fault, 0444, show_temp_fault, NULL, 9),
|
||||
SENSOR_ATTR(temp10_alarm, 0444, show_temp_alarm, NULL, 9),
|
||||
SENSOR_ATTR(temp11_input, 0444, show_temp_value, NULL, 10),
|
||||
SENSOR_ATTR(temp11_fault, 0444, show_temp_fault, NULL, 10),
|
||||
SENSOR_ATTR(temp11_alarm, 0444, show_temp_alarm, NULL, 10),
|
||||
SENSOR_ATTR(temp12_input, 0444, show_temp_value, NULL, 11),
|
||||
SENSOR_ATTR(temp12_fault, 0444, show_temp_fault, NULL, 11),
|
||||
SENSOR_ATTR(temp12_alarm, 0444, show_temp_alarm, NULL, 11),
|
||||
SENSOR_ATTR(temp13_input, 0444, show_temp_value, NULL, 12),
|
||||
SENSOR_ATTR(temp13_fault, 0444, show_temp_fault, NULL, 12),
|
||||
SENSOR_ATTR(temp13_alarm, 0444, show_temp_alarm, NULL, 12),
|
||||
SENSOR_ATTR(temp14_input, 0444, show_temp_value, NULL, 13),
|
||||
SENSOR_ATTR(temp14_fault, 0444, show_temp_fault, NULL, 13),
|
||||
SENSOR_ATTR(temp14_alarm, 0444, show_temp_alarm, NULL, 13),
|
||||
SENSOR_ATTR(temp15_input, 0444, show_temp_value, NULL, 14),
|
||||
SENSOR_ATTR(temp15_fault, 0444, show_temp_fault, NULL, 14),
|
||||
SENSOR_ATTR(temp15_alarm, 0444, show_temp_alarm, NULL, 14),
|
||||
SENSOR_ATTR(temp16_input, 0444, show_temp_value, NULL, 15),
|
||||
SENSOR_ATTR(temp16_fault, 0444, show_temp_fault, NULL, 15),
|
||||
SENSOR_ATTR(temp16_alarm, 0444, show_temp_alarm, NULL, 15),
|
||||
};
|
||||
|
||||
static struct sensor_device_attribute sch5636_fan_attr[] = {
|
||||
SENSOR_ATTR(fan1_input, 0444, show_fan_value, NULL, 0),
|
||||
SENSOR_ATTR(fan1_fault, 0444, show_fan_fault, NULL, 0),
|
||||
SENSOR_ATTR(fan1_alarm, 0444, show_fan_alarm, NULL, 0),
|
||||
SENSOR_ATTR(fan2_input, 0444, show_fan_value, NULL, 1),
|
||||
SENSOR_ATTR(fan2_fault, 0444, show_fan_fault, NULL, 1),
|
||||
SENSOR_ATTR(fan2_alarm, 0444, show_fan_alarm, NULL, 1),
|
||||
SENSOR_ATTR(fan3_input, 0444, show_fan_value, NULL, 2),
|
||||
SENSOR_ATTR(fan3_fault, 0444, show_fan_fault, NULL, 2),
|
||||
SENSOR_ATTR(fan3_alarm, 0444, show_fan_alarm, NULL, 2),
|
||||
SENSOR_ATTR(fan4_input, 0444, show_fan_value, NULL, 3),
|
||||
SENSOR_ATTR(fan4_fault, 0444, show_fan_fault, NULL, 3),
|
||||
SENSOR_ATTR(fan4_alarm, 0444, show_fan_alarm, NULL, 3),
|
||||
SENSOR_ATTR(fan5_input, 0444, show_fan_value, NULL, 4),
|
||||
SENSOR_ATTR(fan5_fault, 0444, show_fan_fault, NULL, 4),
|
||||
SENSOR_ATTR(fan5_alarm, 0444, show_fan_alarm, NULL, 4),
|
||||
SENSOR_ATTR(fan6_input, 0444, show_fan_value, NULL, 5),
|
||||
SENSOR_ATTR(fan6_fault, 0444, show_fan_fault, NULL, 5),
|
||||
SENSOR_ATTR(fan6_alarm, 0444, show_fan_alarm, NULL, 5),
|
||||
SENSOR_ATTR(fan7_input, 0444, show_fan_value, NULL, 6),
|
||||
SENSOR_ATTR(fan7_fault, 0444, show_fan_fault, NULL, 6),
|
||||
SENSOR_ATTR(fan7_alarm, 0444, show_fan_alarm, NULL, 6),
|
||||
SENSOR_ATTR(fan8_input, 0444, show_fan_value, NULL, 7),
|
||||
SENSOR_ATTR(fan8_fault, 0444, show_fan_fault, NULL, 7),
|
||||
SENSOR_ATTR(fan8_alarm, 0444, show_fan_alarm, NULL, 7),
|
||||
};
|
||||
|
||||
static int sch5636_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct sch5636_data *data = platform_get_drvdata(pdev);
|
||||
int i;
|
||||
|
||||
if (data->hwmon_dev)
|
||||
hwmon_device_unregister(data->hwmon_dev);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(sch5636_attr); i++)
|
||||
device_remove_file(&pdev->dev, &sch5636_attr[i].dev_attr);
|
||||
|
||||
for (i = 0; i < SCH5636_NO_TEMPS * 3; i++)
|
||||
device_remove_file(&pdev->dev,
|
||||
&sch5636_temp_attr[i].dev_attr);
|
||||
|
||||
for (i = 0; i < SCH5636_NO_FANS * 3; i++)
|
||||
device_remove_file(&pdev->dev,
|
||||
&sch5636_fan_attr[i].dev_attr);
|
||||
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
kfree(data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __devinit sch5636_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct sch5636_data *data;
|
||||
int i, err, val, revision[2];
|
||||
char id[4];
|
||||
|
||||
data = kzalloc(sizeof(struct sch5636_data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
data->addr = platform_get_resource(pdev, IORESOURCE_IO, 0)->start;
|
||||
mutex_init(&data->update_lock);
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
val = sch56xx_read_virtual_reg(data->addr,
|
||||
SCH5636_REG_FUJITSU_ID + i);
|
||||
if (val < 0) {
|
||||
pr_err("Could not read Fujitsu id byte at %#x\n",
|
||||
SCH5636_REG_FUJITSU_ID + i);
|
||||
err = val;
|
||||
goto error;
|
||||
}
|
||||
id[i] = val;
|
||||
}
|
||||
id[i] = '\0';
|
||||
|
||||
if (strcmp(id, "THS")) {
|
||||
pr_err("Unknown Fujitsu id: %02x%02x%02x\n",
|
||||
id[0], id[1], id[2]);
|
||||
err = -ENODEV;
|
||||
goto error;
|
||||
}
|
||||
|
||||
for (i = 0; i < 2; i++) {
|
||||
val = sch56xx_read_virtual_reg(data->addr,
|
||||
SCH5636_REG_FUJITSU_REV + i);
|
||||
if (val < 0) {
|
||||
err = val;
|
||||
goto error;
|
||||
}
|
||||
revision[i] = val;
|
||||
}
|
||||
pr_info("Found %s chip at %#hx, revison: %d.%02d\n", DEVNAME,
|
||||
data->addr, revision[0], revision[1]);
|
||||
|
||||
/* Read all temp + fan ctrl registers to determine which are active */
|
||||
for (i = 0; i < SCH5636_NO_TEMPS; i++) {
|
||||
val = sch56xx_read_virtual_reg(data->addr,
|
||||
SCH5636_REG_TEMP_CTRL(i));
|
||||
if (unlikely(val < 0)) {
|
||||
err = val;
|
||||
goto error;
|
||||
}
|
||||
data->temp_ctrl[i] = val;
|
||||
}
|
||||
|
||||
for (i = 0; i < SCH5636_NO_FANS; i++) {
|
||||
val = sch56xx_read_virtual_reg(data->addr,
|
||||
SCH5636_REG_FAN_CTRL(i));
|
||||
if (unlikely(val < 0)) {
|
||||
err = val;
|
||||
goto error;
|
||||
}
|
||||
data->fan_ctrl[i] = val;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(sch5636_attr); i++) {
|
||||
err = device_create_file(&pdev->dev,
|
||||
&sch5636_attr[i].dev_attr);
|
||||
if (err)
|
||||
goto error;
|
||||
}
|
||||
|
||||
for (i = 0; i < (SCH5636_NO_TEMPS * 3); i++) {
|
||||
if (data->temp_ctrl[i/3] & SCH5636_TEMP_DEACTIVATED)
|
||||
continue;
|
||||
|
||||
err = device_create_file(&pdev->dev,
|
||||
&sch5636_temp_attr[i].dev_attr);
|
||||
if (err)
|
||||
goto error;
|
||||
}
|
||||
|
||||
for (i = 0; i < (SCH5636_NO_FANS * 3); i++) {
|
||||
if (data->fan_ctrl[i/3] & SCH5636_FAN_DEACTIVATED)
|
||||
continue;
|
||||
|
||||
err = device_create_file(&pdev->dev,
|
||||
&sch5636_fan_attr[i].dev_attr);
|
||||
if (err)
|
||||
goto error;
|
||||
}
|
||||
|
||||
data->hwmon_dev = hwmon_device_register(&pdev->dev);
|
||||
if (IS_ERR(data->hwmon_dev)) {
|
||||
err = PTR_ERR(data->hwmon_dev);
|
||||
data->hwmon_dev = NULL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
sch5636_remove(pdev);
|
||||
return err;
|
||||
}
|
||||
|
||||
static struct platform_driver sch5636_driver = {
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = DRVNAME,
|
||||
},
|
||||
.probe = sch5636_probe,
|
||||
.remove = sch5636_remove,
|
||||
};
|
||||
|
||||
static int __init sch5636_init(void)
|
||||
{
|
||||
return platform_driver_register(&sch5636_driver);
|
||||
}
|
||||
|
||||
static void __exit sch5636_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&sch5636_driver);
|
||||
}
|
||||
|
||||
MODULE_DESCRIPTION("SMSC SCH5636 Hardware Monitoring Driver");
|
||||
MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_init(sch5636_init);
|
||||
module_exit(sch5636_exit);
|
|
@ -0,0 +1,340 @@
|
|||
/***************************************************************************
|
||||
* Copyright (C) 2010-2011 Hans de Goede <hdegoede@redhat.com> *
|
||||
* *
|
||||
* 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. *
|
||||
***************************************************************************/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/delay.h>
|
||||
#include "sch56xx-common.h"
|
||||
|
||||
#define SIO_SCH56XX_LD_EM 0x0C /* Embedded uController Logical Dev */
|
||||
#define SIO_UNLOCK_KEY 0x55 /* Key to enable Super-I/O */
|
||||
#define SIO_LOCK_KEY 0xAA /* Key to disable Super-I/O */
|
||||
|
||||
#define SIO_REG_LDSEL 0x07 /* Logical device select */
|
||||
#define SIO_REG_DEVID 0x20 /* Device ID */
|
||||
#define SIO_REG_ENABLE 0x30 /* Logical device enable */
|
||||
#define SIO_REG_ADDR 0x66 /* Logical device address (2 bytes) */
|
||||
|
||||
#define SIO_SCH5627_ID 0xC6 /* Chipset ID */
|
||||
#define SIO_SCH5636_ID 0xC7 /* Chipset ID */
|
||||
|
||||
#define REGION_LENGTH 9
|
||||
|
||||
#define SCH56XX_CMD_READ 0x02
|
||||
#define SCH56XX_CMD_WRITE 0x03
|
||||
|
||||
static struct platform_device *sch56xx_pdev;
|
||||
|
||||
/* Super I/O functions */
|
||||
static inline int superio_inb(int base, int reg)
|
||||
{
|
||||
outb(reg, base);
|
||||
return inb(base + 1);
|
||||
}
|
||||
|
||||
static inline int superio_enter(int base)
|
||||
{
|
||||
/* Don't step on other drivers' I/O space by accident */
|
||||
if (!request_muxed_region(base, 2, "sch56xx")) {
|
||||
pr_err("I/O address 0x%04x already in use\n", base);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
outb(SIO_UNLOCK_KEY, base);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void superio_select(int base, int ld)
|
||||
{
|
||||
outb(SIO_REG_LDSEL, base);
|
||||
outb(ld, base + 1);
|
||||
}
|
||||
|
||||
static inline void superio_exit(int base)
|
||||
{
|
||||
outb(SIO_LOCK_KEY, base);
|
||||
release_region(base, 2);
|
||||
}
|
||||
|
||||
static int sch56xx_send_cmd(u16 addr, u8 cmd, u16 reg, u8 v)
|
||||
{
|
||||
u8 val;
|
||||
int i;
|
||||
/*
|
||||
* According to SMSC for the commands we use the maximum time for
|
||||
* the EM to respond is 15 ms, but testing shows in practice it
|
||||
* responds within 15-32 reads, so we first busy poll, and if
|
||||
* that fails sleep a bit and try again until we are way past
|
||||
* the 15 ms maximum response time.
|
||||
*/
|
||||
const int max_busy_polls = 64;
|
||||
const int max_lazy_polls = 32;
|
||||
|
||||
/* (Optional) Write-Clear the EC to Host Mailbox Register */
|
||||
val = inb(addr + 1);
|
||||
outb(val, addr + 1);
|
||||
|
||||
/* Set Mailbox Address Pointer to first location in Region 1 */
|
||||
outb(0x00, addr + 2);
|
||||
outb(0x80, addr + 3);
|
||||
|
||||
/* Write Request Packet Header */
|
||||
outb(cmd, addr + 4); /* VREG Access Type read:0x02 write:0x03 */
|
||||
outb(0x01, addr + 5); /* # of Entries: 1 Byte (8-bit) */
|
||||
outb(0x04, addr + 2); /* Mailbox AP to first data entry loc. */
|
||||
|
||||
/* Write Value field */
|
||||
if (cmd == SCH56XX_CMD_WRITE)
|
||||
outb(v, addr + 4);
|
||||
|
||||
/* Write Address field */
|
||||
outb(reg & 0xff, addr + 6);
|
||||
outb(reg >> 8, addr + 7);
|
||||
|
||||
/* Execute the Random Access Command */
|
||||
outb(0x01, addr); /* Write 01h to the Host-to-EC register */
|
||||
|
||||
/* EM Interface Polling "Algorithm" */
|
||||
for (i = 0; i < max_busy_polls + max_lazy_polls; i++) {
|
||||
if (i >= max_busy_polls)
|
||||
msleep(1);
|
||||
/* Read Interrupt source Register */
|
||||
val = inb(addr + 8);
|
||||
/* Write Clear the interrupt source bits */
|
||||
if (val)
|
||||
outb(val, addr + 8);
|
||||
/* Command Completed ? */
|
||||
if (val & 0x01)
|
||||
break;
|
||||
}
|
||||
if (i == max_busy_polls + max_lazy_polls) {
|
||||
pr_err("Max retries exceeded reading virtual "
|
||||
"register 0x%04hx (%d)\n", reg, 1);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/*
|
||||
* According to SMSC we may need to retry this, but sofar I've always
|
||||
* seen this succeed in 1 try.
|
||||
*/
|
||||
for (i = 0; i < max_busy_polls; i++) {
|
||||
/* Read EC-to-Host Register */
|
||||
val = inb(addr + 1);
|
||||
/* Command Completed ? */
|
||||
if (val == 0x01)
|
||||
break;
|
||||
|
||||
if (i == 0)
|
||||
pr_warn("EC reports: 0x%02x reading virtual register "
|
||||
"0x%04hx\n", (unsigned int)val, reg);
|
||||
}
|
||||
if (i == max_busy_polls) {
|
||||
pr_err("Max retries exceeded reading virtual "
|
||||
"register 0x%04hx (%d)\n", reg, 2);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/*
|
||||
* According to the SMSC app note we should now do:
|
||||
*
|
||||
* Set Mailbox Address Pointer to first location in Region 1 *
|
||||
* outb(0x00, addr + 2);
|
||||
* outb(0x80, addr + 3);
|
||||
*
|
||||
* But if we do that things don't work, so let's not.
|
||||
*/
|
||||
|
||||
/* Read Value field */
|
||||
if (cmd == SCH56XX_CMD_READ)
|
||||
return inb(addr + 4);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sch56xx_read_virtual_reg(u16 addr, u16 reg)
|
||||
{
|
||||
return sch56xx_send_cmd(addr, SCH56XX_CMD_READ, reg, 0);
|
||||
}
|
||||
EXPORT_SYMBOL(sch56xx_read_virtual_reg);
|
||||
|
||||
int sch56xx_write_virtual_reg(u16 addr, u16 reg, u8 val)
|
||||
{
|
||||
return sch56xx_send_cmd(addr, SCH56XX_CMD_WRITE, reg, val);
|
||||
}
|
||||
EXPORT_SYMBOL(sch56xx_write_virtual_reg);
|
||||
|
||||
int sch56xx_read_virtual_reg16(u16 addr, u16 reg)
|
||||
{
|
||||
int lsb, msb;
|
||||
|
||||
/* Read LSB first, this will cause the matching MSB to be latched */
|
||||
lsb = sch56xx_read_virtual_reg(addr, reg);
|
||||
if (lsb < 0)
|
||||
return lsb;
|
||||
|
||||
msb = sch56xx_read_virtual_reg(addr, reg + 1);
|
||||
if (msb < 0)
|
||||
return msb;
|
||||
|
||||
return lsb | (msb << 8);
|
||||
}
|
||||
EXPORT_SYMBOL(sch56xx_read_virtual_reg16);
|
||||
|
||||
int sch56xx_read_virtual_reg12(u16 addr, u16 msb_reg, u16 lsn_reg,
|
||||
int high_nibble)
|
||||
{
|
||||
int msb, lsn;
|
||||
|
||||
/* Read MSB first, this will cause the matching LSN to be latched */
|
||||
msb = sch56xx_read_virtual_reg(addr, msb_reg);
|
||||
if (msb < 0)
|
||||
return msb;
|
||||
|
||||
lsn = sch56xx_read_virtual_reg(addr, lsn_reg);
|
||||
if (lsn < 0)
|
||||
return lsn;
|
||||
|
||||
if (high_nibble)
|
||||
return (msb << 4) | (lsn >> 4);
|
||||
else
|
||||
return (msb << 4) | (lsn & 0x0f);
|
||||
}
|
||||
EXPORT_SYMBOL(sch56xx_read_virtual_reg12);
|
||||
|
||||
static int __init sch56xx_find(int sioaddr, unsigned short *address,
|
||||
const char **name)
|
||||
{
|
||||
u8 devid;
|
||||
int err;
|
||||
|
||||
err = superio_enter(sioaddr);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
devid = superio_inb(sioaddr, SIO_REG_DEVID);
|
||||
switch (devid) {
|
||||
case SIO_SCH5627_ID:
|
||||
*name = "sch5627";
|
||||
break;
|
||||
case SIO_SCH5636_ID:
|
||||
*name = "sch5636";
|
||||
break;
|
||||
default:
|
||||
pr_debug("Unsupported device id: 0x%02x\n",
|
||||
(unsigned int)devid);
|
||||
err = -ENODEV;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
superio_select(sioaddr, SIO_SCH56XX_LD_EM);
|
||||
|
||||
if (!(superio_inb(sioaddr, SIO_REG_ENABLE) & 0x01)) {
|
||||
pr_warn("Device not activated\n");
|
||||
err = -ENODEV;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/*
|
||||
* Warning the order of the low / high byte is the other way around
|
||||
* as on most other superio devices!!
|
||||
*/
|
||||
*address = superio_inb(sioaddr, SIO_REG_ADDR) |
|
||||
superio_inb(sioaddr, SIO_REG_ADDR + 1) << 8;
|
||||
if (*address == 0) {
|
||||
pr_warn("Base address not set\n");
|
||||
err = -ENODEV;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
exit:
|
||||
superio_exit(sioaddr);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int __init sch56xx_device_add(unsigned short address, const char *name)
|
||||
{
|
||||
struct resource res = {
|
||||
.start = address,
|
||||
.end = address + REGION_LENGTH - 1,
|
||||
.flags = IORESOURCE_IO,
|
||||
};
|
||||
int err;
|
||||
|
||||
sch56xx_pdev = platform_device_alloc(name, address);
|
||||
if (!sch56xx_pdev)
|
||||
return -ENOMEM;
|
||||
|
||||
res.name = sch56xx_pdev->name;
|
||||
err = acpi_check_resource_conflict(&res);
|
||||
if (err)
|
||||
goto exit_device_put;
|
||||
|
||||
err = platform_device_add_resources(sch56xx_pdev, &res, 1);
|
||||
if (err) {
|
||||
pr_err("Device resource addition failed\n");
|
||||
goto exit_device_put;
|
||||
}
|
||||
|
||||
err = platform_device_add(sch56xx_pdev);
|
||||
if (err) {
|
||||
pr_err("Device addition failed\n");
|
||||
goto exit_device_put;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
exit_device_put:
|
||||
platform_device_put(sch56xx_pdev);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int __init sch56xx_init(void)
|
||||
{
|
||||
int err;
|
||||
unsigned short address;
|
||||
const char *name;
|
||||
|
||||
err = sch56xx_find(0x4e, &address, &name);
|
||||
if (err)
|
||||
err = sch56xx_find(0x2e, &address, &name);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return sch56xx_device_add(address, name);
|
||||
}
|
||||
|
||||
static void __exit sch56xx_exit(void)
|
||||
{
|
||||
platform_device_unregister(sch56xx_pdev);
|
||||
}
|
||||
|
||||
MODULE_DESCRIPTION("SMSC SCH56xx Hardware Monitoring Common Code");
|
||||
MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_init(sch56xx_init);
|
||||
module_exit(sch56xx_exit);
|
|
@ -0,0 +1,24 @@
|
|||
/***************************************************************************
|
||||
* Copyright (C) 2010-2011 Hans de Goede <hdegoede@redhat.com> *
|
||||
* *
|
||||
* 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. *
|
||||
***************************************************************************/
|
||||
|
||||
int sch56xx_read_virtual_reg(u16 addr, u16 reg);
|
||||
int sch56xx_write_virtual_reg(u16 addr, u16 reg, u8 val);
|
||||
int sch56xx_read_virtual_reg16(u16 addr, u16 reg);
|
||||
int sch56xx_read_virtual_reg12(u16 addr, u16 msb_reg, u16 lsn_reg,
|
||||
int high_nibble);
|
|
@ -671,7 +671,7 @@ static ssize_t sht15_show_status(struct device *dev,
|
|||
* @buf: sysfs buffer to read the new heater state from.
|
||||
* @count: length of the data.
|
||||
*
|
||||
* Will be called on read access to heater_enable sysfs attribute.
|
||||
* Will be called on write access to heater_enable sysfs attribute.
|
||||
* Returns number of bytes actually decoded, negative errno on error.
|
||||
*/
|
||||
static ssize_t sht15_store_heater(struct device *dev,
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/hwmon-vid.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/err.h>
|
||||
|
@ -48,8 +49,10 @@ enum { SHOW_TEMP, SHOW_LABEL, SHOW_NAME };
|
|||
struct via_cputemp_data {
|
||||
struct device *hwmon_dev;
|
||||
const char *name;
|
||||
u8 vrm;
|
||||
u32 id;
|
||||
u32 msr;
|
||||
u32 msr_temp;
|
||||
u32 msr_vid;
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -77,13 +80,27 @@ static ssize_t show_temp(struct device *dev,
|
|||
u32 eax, edx;
|
||||
int err;
|
||||
|
||||
err = rdmsr_safe_on_cpu(data->id, data->msr, &eax, &edx);
|
||||
err = rdmsr_safe_on_cpu(data->id, data->msr_temp, &eax, &edx);
|
||||
if (err)
|
||||
return -EAGAIN;
|
||||
|
||||
return sprintf(buf, "%lu\n", ((unsigned long)eax & 0xffffff) * 1000);
|
||||
}
|
||||
|
||||
static ssize_t show_cpu_vid(struct device *dev,
|
||||
struct device_attribute *devattr, char *buf)
|
||||
{
|
||||
struct via_cputemp_data *data = dev_get_drvdata(dev);
|
||||
u32 eax, edx;
|
||||
int err;
|
||||
|
||||
err = rdmsr_safe_on_cpu(data->id, data->msr_vid, &eax, &edx);
|
||||
if (err)
|
||||
return -EAGAIN;
|
||||
|
||||
return sprintf(buf, "%d\n", vid_from_reg(~edx & 0x7f, data->vrm));
|
||||
}
|
||||
|
||||
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL,
|
||||
SHOW_TEMP);
|
||||
static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_name, NULL, SHOW_LABEL);
|
||||
|
@ -100,6 +117,9 @@ static const struct attribute_group via_cputemp_group = {
|
|||
.attrs = via_cputemp_attributes,
|
||||
};
|
||||
|
||||
/* Optional attributes */
|
||||
static DEVICE_ATTR(cpu0_vid, S_IRUGO, show_cpu_vid, NULL);
|
||||
|
||||
static int __devinit via_cputemp_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct via_cputemp_data *data;
|
||||
|
@ -122,11 +142,12 @@ static int __devinit via_cputemp_probe(struct platform_device *pdev)
|
|||
/* C7 A */
|
||||
case 0xD:
|
||||
/* C7 D */
|
||||
data->msr = 0x1169;
|
||||
data->msr_temp = 0x1169;
|
||||
data->msr_vid = 0x198;
|
||||
break;
|
||||
case 0xF:
|
||||
/* Nano */
|
||||
data->msr = 0x1423;
|
||||
data->msr_temp = 0x1423;
|
||||
break;
|
||||
default:
|
||||
err = -ENODEV;
|
||||
|
@ -134,7 +155,7 @@ static int __devinit via_cputemp_probe(struct platform_device *pdev)
|
|||
}
|
||||
|
||||
/* test if we can access the TEMPERATURE MSR */
|
||||
err = rdmsr_safe_on_cpu(data->id, data->msr, &eax, &edx);
|
||||
err = rdmsr_safe_on_cpu(data->id, data->msr_temp, &eax, &edx);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev,
|
||||
"Unable to access TEMPERATURE MSR, giving up\n");
|
||||
|
@ -147,6 +168,15 @@ static int __devinit via_cputemp_probe(struct platform_device *pdev)
|
|||
if (err)
|
||||
goto exit_free;
|
||||
|
||||
if (data->msr_vid)
|
||||
data->vrm = vid_which_vrm();
|
||||
|
||||
if (data->vrm) {
|
||||
err = device_create_file(&pdev->dev, &dev_attr_cpu0_vid);
|
||||
if (err)
|
||||
goto exit_remove;
|
||||
}
|
||||
|
||||
data->hwmon_dev = hwmon_device_register(&pdev->dev);
|
||||
if (IS_ERR(data->hwmon_dev)) {
|
||||
err = PTR_ERR(data->hwmon_dev);
|
||||
|
@ -158,6 +188,8 @@ static int __devinit via_cputemp_probe(struct platform_device *pdev)
|
|||
return 0;
|
||||
|
||||
exit_remove:
|
||||
if (data->vrm)
|
||||
device_remove_file(&pdev->dev, &dev_attr_cpu0_vid);
|
||||
sysfs_remove_group(&pdev->dev.kobj, &via_cputemp_group);
|
||||
exit_free:
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
|
@ -171,6 +203,8 @@ static int __devexit via_cputemp_remove(struct platform_device *pdev)
|
|||
struct via_cputemp_data *data = platform_get_drvdata(pdev);
|
||||
|
||||
hwmon_device_unregister(data->hwmon_dev);
|
||||
if (data->vrm)
|
||||
device_remove_file(&pdev->dev, &dev_attr_cpu0_vid);
|
||||
sysfs_remove_group(&pdev->dev.kobj, &via_cputemp_group);
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
kfree(data);
|
||||
|
|
Loading…
Reference in New Issue