diff options
Diffstat (limited to 'drivers/hwmon')
-rw-r--r-- | drivers/hwmon/Kconfig | 56 | ||||
-rw-r--r-- | drivers/hwmon/Makefile | 3 | ||||
-rw-r--r-- | drivers/hwmon/ams/Makefile | 8 | ||||
-rw-r--r-- | drivers/hwmon/ams/ams-core.c | 265 | ||||
-rw-r--r-- | drivers/hwmon/ams/ams-i2c.c | 299 | ||||
-rw-r--r-- | drivers/hwmon/ams/ams-input.c | 160 | ||||
-rw-r--r-- | drivers/hwmon/ams/ams-pmu.c | 207 | ||||
-rw-r--r-- | drivers/hwmon/ams/ams.h | 72 | ||||
-rw-r--r-- | drivers/hwmon/f71805f.c | 569 | ||||
-rw-r--r-- | drivers/hwmon/hdaps.c | 68 | ||||
-rw-r--r-- | drivers/hwmon/hwmon-vid.c | 4 | ||||
-rw-r--r-- | drivers/hwmon/it87.c | 202 | ||||
-rw-r--r-- | drivers/hwmon/k8temp.c | 4 | ||||
-rw-r--r-- | drivers/hwmon/pc87360.c | 2 | ||||
-rw-r--r-- | drivers/hwmon/pc87427.c | 627 | ||||
-rw-r--r-- | drivers/hwmon/w83627ehf.c | 2 | ||||
-rw-r--r-- | drivers/hwmon/w83792d.c | 2 | ||||
-rw-r--r-- | drivers/hwmon/w83793.c | 1609 |
18 files changed, 3882 insertions, 277 deletions
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index e76d91906c99..891ef6d0b1bf 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -106,6 +106,31 @@ config SENSORS_K8TEMP This driver can also be built as a module. If so, the module will be called k8temp. +config SENSORS_AMS + tristate "Apple Motion Sensor driver" + depends on HWMON && PPC_PMAC && !PPC64 && INPUT && ((ADB_PMU && I2C = y) || (ADB_PMU && !I2C) || I2C) && EXPERIMENTAL + help + Support for the motion sensor included in PowerBooks. Includes + implementations for PMU and I2C. + + This driver can also be built as a module. If so, the module + will be called ams. + +config SENSORS_AMS_PMU + bool "PMU variant" + depends on SENSORS_AMS && ADB_PMU + default y + help + PMU variant of motion sensor, found in late 2005 PowerBooks. + +config SENSORS_AMS_I2C + bool "I2C variant" + depends on SENSORS_AMS && I2C + default y + help + I2C variant of motion sensor, found in early 2005 PowerBooks and + iBooks. + config SENSORS_ASB100 tristate "Asus ASB100 Bach" depends on HWMON && I2C && EXPERIMENTAL @@ -142,11 +167,12 @@ config SENSORS_DS1621 will be called ds1621. config SENSORS_F71805F - tristate "Fintek F71805F/FG" + tristate "Fintek F71805F/FG and F71872F/FG" depends on HWMON && EXPERIMENTAL help If you say yes here you get support for hardware monitoring - features of the Fintek F71805F/FG chips. + features of the Fintek F71805F/FG and F71872F/FG Super-I/O + chips. This driver can also be built as a module. If so, the module will be called f71805f. @@ -353,6 +379,19 @@ config SENSORS_PC87360 This driver can also be built as a module. If so, the module will be called pc87360. +config SENSORS_PC87427 + tristate "National Semiconductor PC87427" + depends on HWMON && EXPERIMENTAL + help + If you say yes here you get access to the hardware monitoring + functions of the National Semiconductor PC87427 Super-I/O chip. + The chip has two distinct logical devices, one for fan speed + monitoring and control, and one for voltage and temperature + monitoring. Only fan speed monitoring is supported right now. + + This driver can also be built as a module. If so, the module + will be called pc87427. + config SENSORS_SIS5595 tristate "Silicon Integrated Systems Corp. SiS5595" depends on HWMON && I2C && PCI && EXPERIMENTAL @@ -474,6 +513,16 @@ config SENSORS_W83792D This driver can also be built as a module. If so, the module will be called w83792d. +config SENSORS_W83793 + tristate "Winbond W83793" + depends on HWMON && I2C && EXPERIMENTAL + help + If you say yes here you get support for the Winbond W83793 + hardware monitoring chip. + + This driver can also be built as a module. If so, the module + will be called w83793. + config SENSORS_W83L785TS tristate "Winbond W83L785TS-S" depends on HWMON && I2C && EXPERIMENTAL @@ -527,6 +576,9 @@ config SENSORS_HDAPS This driver also provides an absolute input class device, allowing the laptop to act as a pinball machine-esque joystick. + If your ThinkPad is not recognized by the driver, please update to latest + BIOS. This is especially the case for some R52 ThinkPads. + Say Y here if you have an applicable laptop and want to experience the awesome power of hdaps. diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index af01cc64f7d2..31661124271e 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_HWMON_VID) += hwmon-vid.o obj-$(CONFIG_SENSORS_ASB100) += asb100.o obj-$(CONFIG_SENSORS_W83627HF) += w83627hf.o obj-$(CONFIG_SENSORS_W83792D) += w83792d.o +obj-$(CONFIG_SENSORS_W83793) += w83793.o obj-$(CONFIG_SENSORS_W83781D) += w83781d.o obj-$(CONFIG_SENSORS_W83791D) += w83791d.o @@ -18,6 +19,7 @@ obj-$(CONFIG_SENSORS_ADM1025) += adm1025.o obj-$(CONFIG_SENSORS_ADM1026) += adm1026.o obj-$(CONFIG_SENSORS_ADM1031) += adm1031.o obj-$(CONFIG_SENSORS_ADM9240) += adm9240.o +obj-$(CONFIG_SENSORS_AMS) += ams/ obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o obj-$(CONFIG_SENSORS_DS1621) += ds1621.o obj-$(CONFIG_SENSORS_F71805F) += f71805f.o @@ -41,6 +43,7 @@ obj-$(CONFIG_SENSORS_LM90) += lm90.o obj-$(CONFIG_SENSORS_LM92) += lm92.o obj-$(CONFIG_SENSORS_MAX1619) += max1619.o obj-$(CONFIG_SENSORS_PC87360) += pc87360.o +obj-$(CONFIG_SENSORS_PC87427) += pc87427.o obj-$(CONFIG_SENSORS_SIS5595) += sis5595.o obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o diff --git a/drivers/hwmon/ams/Makefile b/drivers/hwmon/ams/Makefile new file mode 100644 index 000000000000..41c95b2089dc --- /dev/null +++ b/drivers/hwmon/ams/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for Apple Motion Sensor driver +# + +ams-y := ams-core.o ams-input.o +ams-$(CONFIG_SENSORS_AMS_PMU) += ams-pmu.o +ams-$(CONFIG_SENSORS_AMS_I2C) += ams-i2c.o +obj-$(CONFIG_SENSORS_AMS) += ams.o diff --git a/drivers/hwmon/ams/ams-core.c b/drivers/hwmon/ams/ams-core.c new file mode 100644 index 000000000000..f1f0f5d0442c --- /dev/null +++ b/drivers/hwmon/ams/ams-core.c @@ -0,0 +1,265 @@ +/* + * Apple Motion Sensor driver + * + * Copyright (C) 2005 Stelian Pop (stelian@popies.net) + * Copyright (C) 2006 Michael Hanselmann (linux-kernel@hansmi.ch) + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/module.h> +#include <asm/pmac_pfunc.h> +#include <asm/of_platform.h> + +#include "ams.h" + +/* There is only one motion sensor per machine */ +struct ams ams_info; + +static unsigned int verbose; +module_param(verbose, bool, 0644); +MODULE_PARM_DESC(verbose, "Show free falls and shocks in kernel output"); + +/* Call with ams_info.lock held! */ +void ams_sensors(s8 *x, s8 *y, s8 *z) +{ + u32 orient = ams_info.vflag? ams_info.orient1 : ams_info.orient2; + + if (orient & 0x80) + /* X and Y swapped */ + ams_info.get_xyz(y, x, z); + else + ams_info.get_xyz(x, y, z); + + if (orient & 0x04) + *z = ~(*z); + if (orient & 0x02) + *y = ~(*y); + if (orient & 0x01) + *x = ~(*x); +} + +static ssize_t ams_show_current(struct device *dev, + struct device_attribute *attr, char *buf) +{ + s8 x, y, z; + + mutex_lock(&ams_info.lock); + ams_sensors(&x, &y, &z); + mutex_unlock(&ams_info.lock); + + return snprintf(buf, PAGE_SIZE, "%d %d %d\n", x, y, z); +} + +static DEVICE_ATTR(current, S_IRUGO, ams_show_current, NULL); + +static void ams_handle_irq(void *data) +{ + enum ams_irq irq = *((enum ams_irq *)data); + + spin_lock(&ams_info.irq_lock); + + ams_info.worker_irqs |= irq; + schedule_work(&ams_info.worker); + + spin_unlock(&ams_info.irq_lock); +} + +static enum ams_irq ams_freefall_irq_data = AMS_IRQ_FREEFALL; +static struct pmf_irq_client ams_freefall_client = { + .owner = THIS_MODULE, + .handler = ams_handle_irq, + .data = &ams_freefall_irq_data, +}; + +static enum ams_irq ams_shock_irq_data = AMS_IRQ_SHOCK; +static struct pmf_irq_client ams_shock_client = { + .owner = THIS_MODULE, + .handler = ams_handle_irq, + .data = &ams_shock_irq_data, +}; + +/* Once hard disk parking is implemented in the kernel, this function can + * trigger it. + */ +static void ams_worker(struct work_struct *work) +{ + mutex_lock(&ams_info.lock); + + if (ams_info.has_device) { + unsigned long flags; + + spin_lock_irqsave(&ams_info.irq_lock, flags); + + if (ams_info.worker_irqs & AMS_IRQ_FREEFALL) { + if (verbose) + printk(KERN_INFO "ams: freefall detected!\n"); + + ams_info.worker_irqs &= ~AMS_IRQ_FREEFALL; + + /* we must call this with interrupts enabled */ + spin_unlock_irqrestore(&ams_info.irq_lock, flags); + ams_info.clear_irq(AMS_IRQ_FREEFALL); + spin_lock_irqsave(&ams_info.irq_lock, flags); + } + + if (ams_info.worker_irqs & AMS_IRQ_SHOCK) { + if (verbose) + printk(KERN_INFO "ams: shock detected!\n"); + + ams_info.worker_irqs &= ~AMS_IRQ_SHOCK; + + /* we must call this with interrupts enabled */ + spin_unlock_irqrestore(&ams_info.irq_lock, flags); + ams_info.clear_irq(AMS_IRQ_SHOCK); + spin_lock_irqsave(&ams_info.irq_lock, flags); + } + + spin_unlock_irqrestore(&ams_info.irq_lock, flags); + } + + mutex_unlock(&ams_info.lock); +} + +/* Call with ams_info.lock held! */ +int ams_sensor_attach(void) +{ + int result; + u32 *prop; + + /* Get orientation */ + prop = (u32*)get_property(ams_info.of_node, "orientation", NULL); + if (!prop) + return -ENODEV; + ams_info.orient1 = *prop; + ams_info.orient2 = *(prop + 1); + + /* Register freefall interrupt handler */ + result = pmf_register_irq_client(ams_info.of_node, + "accel-int-1", + &ams_freefall_client); + if (result < 0) + return -ENODEV; + + /* Reset saved irqs */ + ams_info.worker_irqs = 0; + + /* Register shock interrupt handler */ + result = pmf_register_irq_client(ams_info.of_node, + "accel-int-2", + &ams_shock_client); + if (result < 0) + goto release_freefall; + + /* Create device */ + ams_info.of_dev = of_platform_device_create(ams_info.of_node, "ams", NULL); + if (!ams_info.of_dev) { + result = -ENODEV; + goto release_shock; + } + + /* Create attributes */ + result = device_create_file(&ams_info.of_dev->dev, &dev_attr_current); + if (result) + goto release_of; + + ams_info.vflag = !!(ams_info.get_vendor() & 0x10); + + /* Init input device */ + result = ams_input_init(); + if (result) + goto release_device_file; + + return result; +release_device_file: + device_remove_file(&ams_info.of_dev->dev, &dev_attr_current); +release_of: + of_device_unregister(ams_info.of_dev); +release_shock: + pmf_unregister_irq_client(&ams_shock_client); +release_freefall: + pmf_unregister_irq_client(&ams_freefall_client); + return result; +} + +int __init ams_init(void) +{ + struct device_node *np; + + spin_lock_init(&ams_info.irq_lock); + mutex_init(&ams_info.lock); + INIT_WORK(&ams_info.worker, ams_worker); + +#ifdef CONFIG_SENSORS_AMS_I2C + np = of_find_node_by_name(NULL, "accelerometer"); + if (np && device_is_compatible(np, "AAPL,accelerometer_1")) + /* Found I2C motion sensor */ + return ams_i2c_init(np); +#endif + +#ifdef CONFIG_SENSORS_AMS_PMU + np = of_find_node_by_name(NULL, "sms"); + if (np && device_is_compatible(np, "sms")) + /* Found PMU motion sensor */ + return ams_pmu_init(np); +#endif + + printk(KERN_ERR "ams: No motion sensor found.\n"); + + return -ENODEV; +} + +void ams_exit(void) +{ + mutex_lock(&ams_info.lock); + + if (ams_info.has_device) { + /* Remove input device */ + ams_input_exit(); + + /* Shut down implementation */ + ams_info.exit(); + + /* Flush interrupt worker + * + * We do this after ams_info.exit(), because an interrupt might + * have arrived before disabling them. + */ + flush_scheduled_work(); + + /* Remove attributes */ + device_remove_file(&ams_info.of_dev->dev, &dev_attr_current); + + /* Remove device */ + of_device_unregister(ams_info.of_dev); + + /* Remove handler */ + pmf_unregister_irq_client(&ams_shock_client); + pmf_unregister_irq_client(&ams_freefall_client); + } + + mutex_unlock(&ams_info.lock); +} + +MODULE_AUTHOR("Stelian Pop, Michael Hanselmann"); +MODULE_DESCRIPTION("Apple Motion Sensor driver"); +MODULE_LICENSE("GPL"); + +module_init(ams_init); +module_exit(ams_exit); diff --git a/drivers/hwmon/ams/ams-i2c.c b/drivers/hwmon/ams/ams-i2c.c new file mode 100644 index 000000000000..0d24bdfea53e --- /dev/null +++ b/drivers/hwmon/ams/ams-i2c.c @@ -0,0 +1,299 @@ +/* + * Apple Motion Sensor driver (I2C variant) + * + * Copyright (C) 2005 Stelian Pop (stelian@popies.net) + * Copyright (C) 2006 Michael Hanselmann (linux-kernel@hansmi.ch) + * + * Clean room implementation based on the reverse engineered Mac OS X driver by + * Johannes Berg <johannes@sipsolutions.net>, documentation available at + * http://johannes.sipsolutions.net/PowerBook/Apple_Motion_Sensor_Specification + * + * 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. + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/delay.h> + +#include "ams.h" + +/* AMS registers */ +#define AMS_COMMAND 0x00 /* command register */ +#define AMS_STATUS 0x01 /* status register */ +#define AMS_CTRL1 0x02 /* read control 1 (number of values) */ +#define AMS_CTRL2 0x03 /* read control 2 (offset?) */ +#define AMS_CTRL3 0x04 /* read control 3 (size of each value?) */ +#define AMS_DATA1 0x05 /* read data 1 */ +#define AMS_DATA2 0x06 /* read data 2 */ +#define AMS_DATA3 0x07 /* read data 3 */ +#define AMS_DATA4 0x08 /* read data 4 */ +#define AMS_DATAX 0x20 /* data X */ +#define AMS_DATAY 0x21 /* data Y */ +#define AMS_DATAZ 0x22 /* data Z */ +#define AMS_FREEFALL 0x24 /* freefall int control */ +#define AMS_SHOCK 0x25 /* shock int control */ +#define AMS_SENSLOW 0x26 /* sensitivity low limit */ +#define AMS_SENSHIGH 0x27 /* sensitivity high limit */ +#define AMS_CTRLX 0x28 /* control X */ +#define AMS_CTRLY 0x29 /* control Y */ +#define AMS_CTRLZ 0x2A /* control Z */ +#define AMS_UNKNOWN1 0x2B /* unknown 1 */ +#define AMS_UNKNOWN2 0x2C /* unknown 2 */ +#define AMS_UNKNOWN3 0x2D /* unknown 3 */ +#define AMS_VENDOR 0x2E /* vendor */ + +/* AMS commands - use with the AMS_COMMAND register */ +enum ams_i2c_cmd { + AMS_CMD_NOOP = 0, + AMS_CMD_VERSION, + AMS_CMD_READMEM, + AMS_CMD_WRITEMEM, + AMS_CMD_ERASEMEM, + AMS_CMD_READEE, + AMS_CMD_WRITEEE, + AMS_CMD_RESET, + AMS_CMD_START, +}; + +static int ams_i2c_attach(struct i2c_adapter *adapter); +static int ams_i2c_detach(struct i2c_adapter *adapter); + +static struct i2c_driver ams_i2c_driver = { + .driver = { + .name = "ams", + .owner = THIS_MODULE, + }, + .attach_adapter = ams_i2c_attach, + .detach_adapter = ams_i2c_detach, +}; + +static s32 ams_i2c_read(u8 reg) +{ + return i2c_smbus_read_byte_data(&ams_info.i2c_client, reg); +} + +static int ams_i2c_write(u8 reg, u8 value) +{ + return i2c_smbus_write_byte_data(&ams_info.i2c_client, reg, value); +} + +static int ams_i2c_cmd(enum ams_i2c_cmd cmd) +{ + s32 result; + int remaining = HZ / 20; + + ams_i2c_write(AMS_COMMAND, cmd); + mdelay(5); + + while (remaining) { + result = ams_i2c_read(AMS_COMMAND); + if (result == 0 || result & 0x80) + return 0; + + remaining = schedule_timeout(remaining); + } + + return -1; +} + +static void ams_i2c_set_irq(enum ams_irq reg, char enable) +{ + if (reg & AMS_IRQ_FREEFALL) { + u8 val = ams_i2c_read(AMS_CTRLX); + if (enable) + val |= 0x80; + else + val &= ~0x80; + ams_i2c_write(AMS_CTRLX, val); + } + + if (reg & AMS_IRQ_SHOCK) { + u8 val = ams_i2c_read(AMS_CTRLY); + if (enable) + val |= 0x80; + else + val &= ~0x80; + ams_i2c_write(AMS_CTRLY, val); + } + + if (reg & AMS_IRQ_GLOBAL) { + u8 val = ams_i2c_read(AMS_CTRLZ); + if (enable) + val |= 0x80; + else + val &= ~0x80; + ams_i2c_write(AMS_CTRLZ, val); + } +} + +static void ams_i2c_clear_irq(enum ams_irq reg) +{ + if (reg & AMS_IRQ_FREEFALL) + ams_i2c_write(AMS_FREEFALL, 0); + + if (reg & AMS_IRQ_SHOCK) + ams_i2c_write(AMS_SHOCK, 0); +} + +static u8 ams_i2c_get_vendor(void) +{ + return ams_i2c_read(AMS_VENDOR); +} + +static void ams_i2c_get_xyz(s8 *x, s8 *y, s8 *z) +{ + *x = ams_i2c_read(AMS_DATAX); + *y = ams_i2c_read(AMS_DATAY); + *z = ams_i2c_read(AMS_DATAZ); +} + +static int ams_i2c_attach(struct i2c_adapter *adapter) +{ + unsigned long bus; + int vmaj, vmin; + int result; + + /* There can be only one */ + if (unlikely(ams_info.has_device)) + return -ENODEV; + + if (strncmp(adapter->name, "uni-n", 5)) + return -ENODEV; + + bus = simple_strtoul(adapter->name + 6, NULL, 10); + if (bus != ams_info.i2c_bus) + return -ENODEV; + + ams_info.i2c_client.addr = ams_info.i2c_address; + ams_info.i2c_client.adapter = adapter; + ams_info.i2c_client.driver = &ams_i2c_driver; + strcpy(ams_info.i2c_client.name, "Apple Motion Sensor"); + + if (ams_i2c_cmd(AMS_CMD_RESET)) { + printk(KERN_INFO "ams: Failed to reset the device\n"); + return -ENODEV; + } + + if (ams_i2c_cmd(AMS_CMD_START)) { + printk(KERN_INFO "ams: Failed to start the device\n"); + return -ENODEV; + } + + /* get version/vendor information */ + ams_i2c_write(AMS_CTRL1, 0x02); + ams_i2c_write(AMS_CTRL2, 0x85); + ams_i2c_write(AMS_CTRL3, 0x01); + + ams_i2c_cmd(AMS_CMD_READMEM); + + vmaj = ams_i2c_read(AMS_DATA1); + vmin = ams_i2c_read(AMS_DATA2); + if (vmaj != 1 || vmin != 52) { + printk(KERN_INFO "ams: Incorrect device version (%d.%d)\n", + vmaj, vmin); + return -ENODEV; + } + + ams_i2c_cmd(AMS_CMD_VERSION); + + vmaj = ams_i2c_read(AMS_DATA1); + vmin = ams_i2c_read(AMS_DATA2); + if (vmaj != 0 || vmin != 1) { + printk(KERN_INFO "ams: Incorrect firmware version (%d.%d)\n", + vmaj, vmin); + return -ENODEV; + } + + /* Disable interrupts */ + ams_i2c_set_irq(AMS_IRQ_ALL, 0); + + result = ams_sensor_attach(); + if (result < 0) + return result; + + /* Set default values */ + ams_i2c_write(AMS_SENSLOW, 0x15); + ams_i2c_write(AMS_SENSHIGH, 0x60); + ams_i2c_write(AMS_CTRLX, 0x08); + ams_i2c_write(AMS_CTRLY, 0x0F); + ams_i2c_write(AMS_CTRLZ, 0x4F); + ams_i2c_write(AMS_UNKNOWN1, 0x14); + + /* Clear interrupts */ + ams_i2c_clear_irq(AMS_IRQ_ALL); + + ams_info.has_device = 1; + + /* Enable interrupts */ + ams_i2c_set_irq(AMS_IRQ_ALL, 1); + + printk(KERN_INFO "ams: Found I2C based motion sensor\n"); + + return 0; +} + +static int ams_i2c_detach(struct i2c_adapter *adapter) +{ + if (ams_info.has_device) { + /* Disable interrupts */ + ams_i2c_set_irq(AMS_IRQ_ALL, 0); + + /* Clear interrupts */ + ams_i2c_clear_irq(AMS_IRQ_ALL); + + printk(KERN_INFO "ams: Unloading\n"); + + ams_info.has_device = 0; + } + + return 0; +} + +static void ams_i2c_exit(void) +{ + i2c_del_driver(&ams_i2c_driver); +} + +int __init ams_i2c_init(struct device_node *np) +{ + char *tmp_bus; + int result; + u32 *prop; + + mutex_lock(&ams_info.lock); + + /* Set implementation stuff */ + ams_info.of_node = np; + ams_info.exit = ams_i2c_exit; + ams_info.get_vendor = ams_i2c_get_vendor; + ams_info.get_xyz = ams_i2c_get_xyz; + ams_info.clear_irq = ams_i2c_clear_irq; + ams_info.bustype = BUS_I2C; + + /* look for bus either using "reg" or by path */ + prop = (u32*)get_property(ams_info.of_node, "reg", NULL); + if (!prop) { + result = -ENODEV; + + goto exit; + } + + tmp_bus = strstr(ams_info.of_node->full_name, "/i2c-bus@"); + if (tmp_bus) + ams_info.i2c_bus = *(tmp_bus + 9) - '0'; + else + ams_info.i2c_bus = ((*prop) >> 8) & 0x0f; + ams_info.i2c_address = ((*prop) & 0xff) >> 1; + + result = i2c_add_driver(&ams_i2c_driver); + +exit: + mutex_unlock(&ams_info.lock); + + return result; +} diff --git a/drivers/hwmon/ams/ams-input.c b/drivers/hwmon/ams/ams-input.c new file mode 100644 index 000000000000..f126aa485134 --- /dev/null +++ b/drivers/hwmon/ams/ams-input.c @@ -0,0 +1,160 @@ +/* + * Apple Motion Sensor driver (joystick emulation) + * + * Copyright (C) 2005 Stelian Pop (stelian@popies.net) + * Copyright (C) 2006 Michael Hanselmann (linux-kernel@hansmi.ch) + * + * 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. + */ + +#include <linux/module.h> + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/delay.h> + +#include "ams.h" + +static unsigned int joystick; +module_param(joystick, bool, 0644); +MODULE_PARM_DESC(joystick, "Enable the input class device on module load"); + +static unsigned int invert; +module_param(invert, bool, 0644); +MODULE_PARM_DESC(invert, "Invert input data on X and Y axis"); + +static int ams_input_kthread(void *data) +{ + s8 x, y, z; + + while (!kthread_should_stop()) { + mutex_lock(&ams_info.lock); + + ams_sensors(&x, &y, &z); + + x -= ams_info.xcalib; + y -= ams_info.ycalib; + z -= ams_info.zcalib; + + input_report_abs(ams_info.idev, ABS_X, invert ? -x : x); + input_report_abs(ams_info.idev, ABS_Y, invert ? -y : y); + input_report_abs(ams_info.idev, ABS_Z, z); + + input_sync(ams_info.idev); + + mutex_unlock(&ams_info.lock); + + msleep(25); + } + + return 0; +} + +static int ams_input_open(struct input_dev *dev) +{ + ams_info.kthread = kthread_run(ams_input_kthread, NULL, "kams"); + return IS_ERR(ams_info.kthread) ? PTR_ERR(ams_info.kthread) : 0; +} + +static void ams_input_close(struct input_dev *dev) +{ + kthread_stop(ams_info.kthread); +} + +/* Call with ams_info.lock held! */ +static void ams_input_enable(void) +{ + s8 x, y, z; + + if (ams_info.idev) + return; + + ams_sensors(&x, &y, &z); + ams_info.xcalib = x; + ams_info.ycalib = y; + ams_info.zcalib = z; + + ams_info.idev = input_allocate_device(); + if (!ams_info.idev) + return; + + ams_info.idev->name = "Apple Motion Sensor"; + ams_info.idev->id.bustype = ams_info.bustype; + ams_info.idev->id.vendor = 0; + ams_info.idev->open = ams_input_open; + ams_info.idev->close = ams_input_close; + ams_info.idev->cdev.dev = &ams_info.of_dev->dev; + + input_set_abs_params(ams_info.idev, ABS_X, -50, 50, 3, 0); + input_set_abs_params(ams_info.idev, ABS_Y, -50, 50, 3, 0); + input_set_abs_params(ams_info.idev, ABS_Z, -50, 50, 3, 0); + + set_bit(EV_ABS, ams_info.idev->evbit); + set_bit(EV_KEY, ams_info.idev->evbit); + set_bit(BTN_TOUCH, ams_info.idev->keybit); + + if (input_register_device(ams_info.idev)) { + input_free_device(ams_info.idev); + ams_info.idev = NULL; + return; + } +} + +/* Call with ams_info.lock held! */ +static void ams_input_disable(void) +{ + if (ams_info.idev) { + input_unregister_device(ams_info.idev); + ams_info.idev = NULL; + } +} + +static ssize_t ams_input_show_joystick(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", joystick); +} + +static ssize_t ams_input_store_joystick(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + if (sscanf(buf, "%d\n", &joystick) != 1) + return -EINVAL; + + mutex_lock(&ams_info.lock); + + if (joystick) + ams_input_enable(); + else + ams_input_disable(); + + mutex_unlock(&ams_info.lock); + + return count; +} + +static DEVICE_ATTR(joystick, S_IRUGO | S_IWUSR, + ams_input_show_joystick, ams_input_store_joystick); + +/* Call with ams_info.lock held! */ +int ams_input_init(void) +{ + int result; + + result = device_create_file(&ams_info.of_dev->dev, &dev_attr_joystick); + + if (!result && joystick) + ams_input_enable(); + return result; +} + +/* Call with ams_info.lock held! */ +void ams_input_exit() +{ + ams_input_disable(); + device_remove_file(&ams_info.of_dev->dev, &dev_attr_joystick); +} diff --git a/drivers/hwmon/ams/ams-pmu.c b/drivers/hwmon/ams/ams-pmu.c new file mode 100644 index 000000000000..4636ae031a53 --- /dev/null +++ b/drivers/hwmon/ams/ams-pmu.c @@ -0,0 +1,207 @@ +/* + * Apple Motion Sensor driver (PMU variant) + * + * Copyright (C) 2006 Michael Hanselmann (linux-kernel@hansmi.ch) + * + * 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. + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/adb.h> +#include <linux/pmu.h> + +#include "ams.h" + +/* Attitude */ +#define AMS_X 0x00 +#define AMS_Y 0x01 +#define AMS_Z 0x02 + +/* Not exactly known, maybe chip vendor */ +#define AMS_VENDOR 0x03 + +/* Freefall registers */ +#define AMS_FF_CLEAR 0x04 +#define AMS_FF_ENABLE 0x05 +#define AMS_FF_LOW_LIMIT 0x06 +#define AMS_FF_DEBOUNCE 0x07 + +/* Shock registers */ +#define AMS_SHOCK_CLEAR 0x08 +#define AMS_SHOCK_ENABLE 0x09 +#define AMS_SHOCK_HIGH_LIMIT 0x0a +#define AMS_SHOCK_DEBOUNCE 0x0b + +/* Global interrupt and power control register */ +#define AMS_CONTROL 0x0c + +static u8 ams_pmu_cmd; + +static void ams_pmu_req_complete(struct adb_request *req) +{ + complete((struct completion *)req->arg); +} + +/* Only call this function from task context */ +static void ams_pmu_set_register(u8 reg, u8 value) +{ + static struct adb_request req; + DECLARE_COMPLETION(req_complete); + + req.arg = &req_complete; + if (pmu_request(&req, ams_pmu_req_complete, 4, ams_pmu_cmd, 0x00, reg, value)) + return; + + wait_for_completion(&req_complete); +} + +/* Only call this function from task context */ +static u8 ams_pmu_get_register(u8 reg) +{ + static struct adb_request req; + DECLARE_COMPLETION(req_complete); + + req.arg = &req_complete; + if (pmu_request(&req, ams_pmu_req_complete, 3, ams_pmu_cmd, 0x01, reg)) + return 0; + + wait_for_completion(&req_complete); + + if (req.reply_len > 0) + return req.reply[0]; + else + return 0; +} + +/* Enables or disables the specified interrupts */ +static void ams_pmu_set_irq(enum ams_irq reg, char enable) +{ + if (reg & AMS_IRQ_FREEFALL) { + u8 val = ams_pmu_get_register(AMS_FF_ENABLE); + if (enable) + val |= 0x80; + else + val &= ~0x80; + ams_pmu_set_register(AMS_FF_ENABLE, val); + } + + if (reg & AMS_IRQ_SHOCK) { + u8 val = ams_pmu_get_register(AMS_SHOCK_ENABLE); + if (enable) + val |= 0x80; + else + val &= ~0x80; + ams_pmu_set_register(AMS_SHOCK_ENABLE, val); + } + + if (reg & AMS_IRQ_GLOBAL) { + u8 val = ams_pmu_get_register(AMS_CONTROL); + if (enable) + val |= 0x80; + else + val &= ~0x80; + ams_pmu_set_register(AMS_CONTROL, val); + } +} + +static void ams_pmu_clear_irq(enum ams_irq reg) +{ + if (reg & AMS_IRQ_FREEFALL) + ams_pmu_set_register(AMS_FF_CLEAR, 0x00); + + if (reg & AMS_IRQ_SHOCK) + ams_pmu_set_register(AMS_SHOCK_CLEAR, 0x00); +} + +static u8 ams_pmu_get_vendor(void) +{ + return ams_pmu_get_register(AMS_VENDOR); +} + +static void ams_pmu_get_xyz(s8 *x, s8 *y, s8 *z) +{ + *x = ams_pmu_get_register(AMS_X); + *y = ams_pmu_get_register(AMS_Y); + *z = ams_pmu_get_register(AMS_Z); +} + +static void ams_pmu_exit(void) +{ + /* Disable interrupts */ + ams_pmu_set_irq(AMS_IRQ_ALL, 0); + + /* Clear interrupts */ + ams_pmu_clear_irq(AMS_IRQ_ALL); + + ams_info.has_device = 0; + + printk(KERN_INFO "ams: Unloading\n"); +} + +int __init ams_pmu_init(struct device_node *np) +{ + u32 *prop; + int result; + + mutex_lock(&ams_info.lock); + + /* Set implementation stuff */ + ams_info.of_node = np; + ams_info.exit = ams_pmu_exit; + ams_info.get_vendor = ams_pmu_get_vendor; + ams_info.get_xyz = ams_pmu_get_xyz; + ams_info.clear_irq = ams_pmu_clear_irq; + ams_info.bustype = BUS_HOST; + + /* Get PMU command, should be 0x4e, but we can never know */ + prop = (u32*)get_property(ams_info.of_node, "reg", NULL); + if (!prop) { + result = -ENODEV; + goto exit; + } + ams_pmu_cmd = ((*prop) >> 8) & 0xff; + + /* Disable interrupts */ + ams_pmu_set_irq(AMS_IRQ_ALL, 0); + + /* Clear interrupts */ + ams_pmu_clear_irq(AMS_IRQ_ALL); + + result = ams_sensor_attach(); + if (result < 0) + goto exit; + + /* Set default values */ + ams_pmu_set_register(AMS_FF_LOW_LIMIT, 0x15); + ams_pmu_set_register(AMS_FF_ENABLE, 0x08); + ams_pmu_set_register(AMS_FF_DEBOUNCE, 0x14); + + ams_pmu_set_register(AMS_SHOCK_HIGH_LIMIT, 0x60); + ams_pmu_set_register(AMS_SHOCK_ENABLE, 0x0f); + ams_pmu_set_register(AMS_SHOCK_DEBOUNCE, 0x14); + + ams_pmu_set_register(AMS_CONTROL, 0x4f); + + /* Clear interrupts */ + ams_pmu_clear_irq(AMS_IRQ_ALL); + + ams_info.has_device = 1; + + /* Enable interrupts */ + ams_pmu_set_irq(AMS_IRQ_ALL, 1); + + printk(KERN_INFO "ams: Found PMU based motion sensor\n"); + + result = 0; + +exit: + mutex_unlock(&ams_info.lock); + + return result; +} diff --git a/drivers/hwmon/ams/ams.h b/drivers/hwmon/ams/ams.h new file mode 100644 index 000000000000..240730e6bcde --- /dev/null +++ b/drivers/hwmon/ams/ams.h @@ -0,0 +1,72 @@ +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/kthread.h> +#include <linux/mutex.h> +#include <linux/spinlock.h> +#include <linux/types.h> +#include <asm/of_device.h> + +enum ams_irq { + AMS_IRQ_FREEFALL = 0x01, + AMS_IRQ_SHOCK = 0x02, + AMS_IRQ_GLOBAL = 0x04, + AMS_IRQ_ALL = + AMS_IRQ_FREEFALL | + AMS_IRQ_SHOCK | + AMS_IRQ_GLOBAL, +}; + +struct ams { + /* Locks */ + spinlock_t irq_lock; + struct mutex lock; + + /* General properties */ + struct device_node *of_node; + struct of_device *of_dev; + char has_device; + char vflag; + u32 orient1; + u32 orient2; + + /* Interrupt worker */ + struct work_struct worker; + u8 worker_irqs; + + /* Implementation + * + * Only call these functions with the main lock held. + */ + void (*exit)(void); + + void (*get_xyz)(s8 *x, s8 *y, s8 *z); + u8 (*get_vendor)(void); + + void (*clear_irq)(enum ams_irq reg); + +#ifdef CONFIG_SENSORS_AMS_I2C + /* I2C properties */ + int i2c_bus; + int i2c_address; + struct i2c_client i2c_client; +#endif + + /* Joystick emulation */ + struct task_struct *kthread; + struct input_dev *idev; + __u16 bustype; + + /* calibrated null values */ + int xcalib, ycalib, zcalib; +}; + +extern struct ams ams_info; + +extern void ams_sensors(s8 *x, s8 *y, s8 *z); +extern int ams_sensor_attach(void); + +extern int ams_pmu_init(struct device_node *np); +extern int ams_i2c_init(struct device_node *np); + +extern int ams_input_init(void); +extern void ams_input_exit(void); diff --git a/drivers/hwmon/f71805f.c b/drivers/hwmon/f71805f.c index de17a72149d9..a272cae8f60e 100644 --- a/drivers/hwmon/f71805f.c +++ b/drivers/hwmon/f71805f.c @@ -1,12 +1,15 @@ /* - * f71805f.c - driver for the Fintek F71805F/FG Super-I/O chip integrated - * hardware monitoring features + * f71805f.c - driver for the Fintek F71805F/FG and F71872F/FG Super-I/O + * chips integrated hardware monitoring features * Copyright (C) 2005-2006 Jean Delvare <khali@linux-fr.org> * * The F71805F/FG is a LPC Super-I/O chip made by Fintek. It integrates * complete hardware monitoring features: voltage, fan and temperature * sensors, and manual and automatic fan speed control. * + * The F71872F/FG is almost the same, with two more voltages monitored, + * and 6 VID inputs. + * * 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 @@ -37,6 +40,7 @@ static struct platform_device *pdev; #define DRVNAME "f71805f" +enum kinds { f71805f, f71872f }; /* * Super-I/O constants and functions @@ -48,11 +52,13 @@ static struct platform_device *pdev; #define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */ #define SIO_REG_DEVREV 0x22 /* Device revision */ #define SIO_REG_MANID 0x23 /* Fintek ID (2 bytes) */ +#define SIO_REG_FNSEL1 0x29 /* Multi Function Select 1 (F71872F) */ #define SIO_REG_ENABLE 0x30 /* Logical device enable */ #define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */ #define SIO_FINTEK_ID 0x1934 #define SIO_F71805F_ID 0x0406 +#define SIO_F71872F_ID 0x0341 static inline int superio_inb(int base, int reg) @@ -96,22 +102,25 @@ superio_exit(int base) * ISA constants */ -#define REGION_LENGTH 2 -#define ADDR_REG_OFFSET 0 -#define DATA_REG_OFFSET 1 +#define REGION_LENGTH 8 +#define ADDR_REG_OFFSET 5 +#define DATA_REG_OFFSET 6 /* * Registers */ -/* in nr from 0 to 8 (8-bit values) */ +/* in nr from 0 to 10 (8-bit values) */ #define F71805F_REG_IN(nr) (0x10 + (nr)) -#define F71805F_REG_IN_HIGH(nr) (0x40 + 2 * (nr)) -#define F71805F_REG_IN_LOW(nr) (0x41 + 2 * (nr)) +#define F71805F_REG_IN_HIGH(nr) ((nr) < 10 ? 0x40 + 2 * (nr) : 0x2E) +#define F71805F_REG_IN_LOW(nr) ((nr) < 10 ? 0x41 + 2 * (nr) : 0x2F) /* fan nr from 0 to 2 (12-bit values, two registers) */ #define F71805F_REG_FAN(nr) (0x20 + 2 * (nr)) #define F71805F_REG_FAN_LOW(nr) (0x28 + 2 * (nr)) +#define F71805F_REG_FAN_TARGET(nr) (0x69 + 16 * (nr)) #define F71805F_REG_FAN_CTRL(nr) (0x60 + 16 * (nr)) +#define F71805F_REG_PWM_FREQ(nr) (0x63 + 16 * (nr)) +#define F71805F_REG_PWM_DUTY(nr) (0x6B + 16 * (nr)) /* temp nr from 0 to 2 (8-bit values) */ #define F71805F_REG_TEMP(nr) (0x1B + (nr)) #define F71805F_REG_TEMP_HIGH(nr) (0x54 + 2 * (nr)) @@ -122,6 +131,14 @@ superio_exit(int base) /* status nr from 0 to 2 */ #define F71805F_REG_STATUS(nr) (0x36 + (nr)) +/* individual register bits */ +#define FAN_CTRL_DC_MODE 0x10 +#define FAN_CTRL_LATCH_FULL 0x08 +#define FAN_CTRL_MODE_MASK 0x03 +#define FAN_CTRL_MODE_SPEED 0x00 +#define FAN_CTRL_MODE_TEMPERATURE 0x01 +#define FAN_CTRL_MODE_MANUAL 0x02 + /* * Data structures and manipulation thereof */ @@ -138,12 +155,16 @@ struct f71805f_data { unsigned long last_limits; /* In jiffies */ /* Register values */ - u8 in[9]; - u8 in_high[9]; - u8 in_low[9]; + u8 in[11]; + u8 in_high[11]; + u8 in_low[11]; + u16 has_in; u16 fan[3]; u16 fan_low[3]; - u8 fan_enabled; /* Read once at init time */ + u16 fan_target[3]; + u8 fan_ctrl[3]; + u8 pwm[3]; + u8 pwm_freq[3]; u8 temp[3]; u8 temp_high[3]; u8 temp_hyst[3]; @@ -151,6 +172,11 @@ struct f71805f_data { unsigned long alarms; }; +struct f71805f_sio_data { + enum kinds kind; + u8 fnsel1; +}; + static inline long in_from_reg(u8 reg) { return (reg * 8); @@ -200,6 +226,33 @@ static inline u16 fan_to_reg(long rpm) return (1500000 / rpm); } +static inline unsigned long pwm_freq_from_reg(u8 reg) +{ + unsigned long clock = (reg & 0x80) ? 48000000UL : 1000000UL; + + reg &= 0x7f; + if (reg == 0) + reg++; + return clock / (reg << 8); +} + +static inline u8 pwm_freq_to_reg(unsigned long val) +{ + if (val >= 187500) /* The highest we can do */ + return 0x80; + if (val >= 1475) /* Use 48 MHz clock */ + return 0x80 | (48000000UL / (val << 8)); + if (val < 31) /* The lowest we can do */ + return 0x7f; + else /* Use 1 MHz clock */ + return 1000000UL / (val << 8); +} + +static inline int pwm_mode_from_reg(u8 reg) +{ + return !(reg & FAN_CTRL_DC_MODE); +} + static inline long temp_from_reg(u8 reg) { return (reg * 1000); @@ -274,16 +327,21 @@ static struct f71805f_data *f71805f_update_device(struct device *dev) /* Limit registers cache is refreshed after 60 seconds */ if (time_after(jiffies, data->last_updated + 60 * HZ) || !data->valid) { - for (nr = 0; nr < 9; nr++) { + for (nr = 0; nr < 11; nr++) { + if (!(data->has_in & (1 << nr))) + continue; data->in_high[nr] = f71805f_read8(data, F71805F_REG_IN_HIGH(nr)); data->in_low[nr] = f71805f_read8(data, F71805F_REG_IN_LOW(nr)); } for (nr = 0; nr < 3; nr++) { - if (data->fan_enabled & (1 << nr)) - data->fan_low[nr] = f71805f_read16(data, - F71805F_REG_FAN_LOW(nr)); + data->fan_low[nr] = f71805f_read16(data, + F71805F_REG_FAN_LOW(nr)); + data->fan_target[nr] = f71805f_read16(data, + F71805F_REG_FAN_TARGET(nr)); + data->pwm_freq[nr] = f71805f_read8(data, + F71805F_REG_PWM_FREQ(nr)); } for (nr = 0; nr < 3; nr++) { data->temp_high[nr] = f71805f_read8(data, @@ -299,14 +357,19 @@ static struct f71805f_data *f71805f_update_device(struct device *dev) /* Measurement registers cache is refreshed after 1 second */ if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { - for (nr = 0; nr < 9; nr++) { + for (nr = 0; nr < 11; nr++) { + if (!(data->has_in & (1 << nr))) + continue; data->in[nr] = f71805f_read8(data, F71805F_REG_IN(nr)); } for (nr = 0; nr < 3; nr++) { - if (data->fan_enabled & (1 << nr)) - data->fan[nr] = f71805f_read16(data, - F71805F_REG_FAN(nr)); + data->fan[nr] = f71805f_read16(data, + F71805F_REG_FAN(nr)); + data->fan_ctrl[nr] = f71805f_read8(data, + F71805F_REG_FAN_CTRL(nr)); + data->pwm[nr] = f71805f_read8(data, + F71805F_REG_PWM_DUTY(nr)); } for (nr = 0; nr < 3; nr++) { data->temp[nr] = f71805f_read8(data, @@ -333,35 +396,43 @@ static ssize_t show_in0(struct device *dev, struct device_attribute *devattr, char *buf) { struct f71805f_data *data = f71805f_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; - return sprintf(buf, "%ld\n", in0_from_reg(data->in[0])); + return sprintf(buf, "%ld\n", in0_from_reg(data->in[nr])); } static ssize_t show_in0_max(struct device *dev, struct device_attribute *devattr, char *buf) { struct f71805f_data *data = f71805f_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; - return sprintf(buf, "%ld\n", in0_from_reg(data->in_high[0])); + return sprintf(buf, "%ld\n", in0_from_reg(data->in_high[nr])); } static ssize_t show_in0_min(struct device *dev, struct device_attribute *devattr, char *buf) { struct f71805f_data *data = f71805f_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; - return sprintf(buf, "%ld\n", in0_from_reg(data->in_low[0])); + return sprintf(buf, "%ld\n", in0_from_reg(data->in_low[nr])); } static ssize_t set_in0_max(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { struct f71805f_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; long val = simple_strtol(buf, NULL, 10); mutex_lock(&data->update_lock); - data->in_high[0] = in0_to_reg(val); - f71805f_write8(data, F71805F_REG_IN_HIGH(0), data->in_high[0]); + data->in_high[nr] = in0_to_reg(val); + f71805f_write8(data, F71805F_REG_IN_HIGH(nr), data->in_high[nr]); mutex_unlock(&data->update_lock); return count; @@ -371,11 +442,13 @@ static ssize_t set_in0_min(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { struct f71805f_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; long val = simple_strtol(buf, NULL, 10); mutex_lock(&data->update_lock); - data->in_low[0] = in0_to_reg(val); - f71805f_write8(data, F71805F_REG_IN_LOW(0), data->in_low[0]); + data->in_low[nr] = in0_to_reg(val); + f71805f_write8(data, F71805F_REG_IN_LOW(nr), data->in_low[nr]); mutex_unlock(&data->update_lock); return count; @@ -463,6 +536,16 @@ static ssize_t show_fan_min(struct device *dev, struct device_attribute return sprintf(buf, "%ld\n", fan_from_reg(data->fan_low[nr])); } +static ssize_t show_fan_target(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct f71805f_data *data = f71805f_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + + return sprintf(buf, "%ld\n", fan_from_reg(data->fan_target[nr])); +} + static ssize_t set_fan_min(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { @@ -479,6 +562,157 @@ static ssize_t set_fan_min(struct device *dev, struct device_attribute return count; } +static ssize_t set_fan_target(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct f71805f_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + long val = simple_strtol(buf, NULL, 10); + + mutex_lock(&data->update_lock); + data->fan_target[nr] = fan_to_reg(val); + f71805f_write16(data, F71805F_REG_FAN_TARGET(nr), + data->fan_target[nr]); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_pwm(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct f71805f_data *data = f71805f_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + + return sprintf(buf, "%d\n", (int)data->pwm[nr]); +} + +static ssize_t show_pwm_enable(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct f71805f_data *data = f71805f_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + int mode; + + switch (data->fan_ctrl[nr] & FAN_CTRL_MODE_MASK) { + case FAN_CTRL_MODE_SPEED: + mode = 3; + break; + case FAN_CTRL_MODE_TEMPERATURE: + mode = 2; + break; + default: /* MANUAL */ + mode = 1; + } + + return sprintf(buf, "%d\n", mode); +} + +static ssize_t show_pwm_freq(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct f71805f_data *data = f71805f_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + + return sprintf(buf, "%lu\n", pwm_freq_from_reg(data->pwm_freq[nr])); +} + +static ssize_t show_pwm_mode(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct f71805f_data *data = f71805f_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + + return sprintf(buf, "%d\n", pwm_mode_from_reg(data->fan_ctrl[nr])); +} + +static ssize_t set_pwm(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct f71805f_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + unsigned long val = simple_strtoul(buf, NULL, 10); + + if (val > 255) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->pwm[nr] = val; + f71805f_write8(data, F71805F_REG_PWM_DUTY(nr), data->pwm[nr]); + mutex_unlock(&data->update_lock); + + return count; +} + +static struct attribute *f71805f_attr_pwm[]; + +static ssize_t set_pwm_enable(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct f71805f_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + unsigned long val = simple_strtoul(buf, NULL, 10); + u8 reg; + + if (val < 1 || val > 3) + return -EINVAL; + + if (val > 1) { /* Automatic mode, user can't set PWM value */ + if (sysfs_chmod_file(&dev->kobj, f71805f_attr_pwm[nr], + S_IRUGO)) + dev_dbg(dev, "chmod -w pwm%d failed\n", nr + 1); + } + + mutex_lock(&data->update_lock); + reg = f71805f_read8(data, F71805F_REG_FAN_CTRL(nr)) + & ~FAN_CTRL_MODE_MASK; + switch (val) { + case 1: + reg |= FAN_CTRL_MODE_MANUAL; + break; + case 2: + reg |= FAN_CTRL_MODE_TEMPERATURE; + break; + case 3: + reg |= FAN_CTRL_MODE_SPEED; + break; + } + data->fan_ctrl[nr] = reg; + f71805f_write8(data, F71805F_REG_FAN_CTRL(nr), reg); + mutex_unlock(&data->update_lock); + + if (val == 1) { /* Manual mode, user can set PWM value */ + if (sysfs_chmod_file(&dev->kobj, f71805f_attr_pwm[nr], + S_IRUGO | S_IWUSR)) + dev_dbg(dev, "chmod +w pwm%d failed\n", nr + 1); + } + + return count; +} + +static ssize_t set_pwm_freq(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct f71805f_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + unsigned long val = simple_strtoul(buf, NULL, 10); + + mutex_lock(&data->update_lock); + data->pwm_freq[nr] = pwm_freq_to_reg(val); + f71805f_write8(data, F71805F_REG_PWM_FREQ(nr), data->pwm_freq[nr]); + mutex_unlock(&data->update_lock); + + return count; +} + static ssize_t show_temp(struct device *dev, struct device_attribute *devattr, char *buf) { @@ -557,7 +791,7 @@ static ssize_t show_alarms_in(struct device *dev, struct device_attribute { struct f71805f_data *data = f71805f_update_device(dev); - return sprintf(buf, "%lu\n", data->alarms & 0x1ff); + return sprintf(buf, "%lu\n", data->alarms & 0x7ff); } static ssize_t show_alarms_fan(struct device *dev, struct device_attribute @@ -594,9 +828,11 @@ static ssize_t show_name(struct device *dev, struct device_attribute return sprintf(buf, "%s\n", data->name); } -static DEVICE_ATTR(in0_input, S_IRUGO, show_in0, NULL); -static DEVICE_ATTR(in0_max, S_IRUGO| S_IWUSR, show_in0_max, set_in0_max); -static DEVICE_ATTR(in0_min, S_IRUGO| S_IWUSR, show_in0_min, set_in0_min); +static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, show_in0, NULL, 0); +static SENSOR_DEVICE_ATTR(in0_max, S_IRUGO| S_IWUSR, + show_in0_max, set_in0_max, 0); +static SENSOR_DEVICE_ATTR(in0_min, S_IRUGO| S_IWUSR, + show_in0_min, set_in0_min, 0); static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, show_in, NULL, 1); static SENSOR_DEVICE_ATTR(in1_max, S_IRUGO | S_IWUSR, show_in_max, set_in_max, 1); @@ -637,16 +873,32 @@ static SENSOR_DEVICE_ATTR(in8_max, S_IRUGO | S_IWUSR, show_in_max, set_in_max, 8); static SENSOR_DEVICE_ATTR(in8_min, S_IRUGO | S_IWUSR, show_in_min, set_in_min, 8); +static SENSOR_DEVICE_ATTR(in9_input, S_IRUGO, show_in0, NULL, 9); +static SENSOR_DEVICE_ATTR(in9_max, S_IRUGO | S_IWUSR, + show_in0_max, set_in0_max, 9); +static SENSOR_DEVICE_ATTR(in9_min, S_IRUGO | S_IWUSR, + show_in0_min, set_in0_min, 9); +static SENSOR_DEVICE_ATTR(in10_input, S_IRUGO, show_in0, NULL, 10); +static SENSOR_DEVICE_ATTR(in10_max, S_IRUGO | S_IWUSR, + show_in0_max, set_in0_max, 10); +static SENSOR_DEVICE_ATTR(in10_min, S_IRUGO | S_IWUSR, + show_in0_min, set_in0_min, 10); static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0); static SENSOR_DEVICE_ATTR(fan1_min, S_IRUGO | S_IWUSR, show_fan_min, set_fan_min, 0); +static SENSOR_DEVICE_ATTR(fan1_target, S_IRUGO | S_IWUSR, + show_fan_target, set_fan_target, 0); static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1); static SENSOR_DEVICE_ATTR(fan2_min, S_IRUGO | S_IWUSR, show_fan_min, set_fan_min, 1); +static SENSOR_DEVICE_ATTR(fan2_target, S_IRUGO | S_IWUSR, + show_fan_target, set_fan_target, 1); static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 2); static SENSOR_DEVICE_ATTR(fan3_min, S_IRUGO | S_IWUSR, show_fan_min, set_fan_min, 2); +static SENSOR_DEVICE_ATTR(fan3_target, S_IRUGO | S_IWUSR, + show_fan_target, set_fan_target, 2); static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0); static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO | S_IWUSR, @@ -667,6 +919,27 @@ static SENSOR_DEVICE_ATTR(temp3_max_hyst, S_IRUGO | S_IWUSR, show_temp_hyst, set_temp_hyst, 2); static SENSOR_DEVICE_ATTR(temp3_type, S_IRUGO, show_temp_type, NULL, 2); +/* pwm (value) files are created read-only, write permission is + then added or removed dynamically as needed */ +static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO, show_pwm, set_pwm, 0); +static SENSOR_DEVICE_ATTR(pwm1_enable, S_IRUGO | S_IWUSR, + show_pwm_enable, set_pwm_enable, 0); +static SENSOR_DEVICE_ATTR(pwm1_freq, S_IRUGO | S_IWUSR, + show_pwm_freq, set_pwm_freq, 0); +static SENSOR_DEVICE_ATTR(pwm1_mode, S_IRUGO, show_pwm_mode, NULL, 0); +static SENSOR_DEVICE_ATTR(pwm2, S_IRUGO, show_pwm, set_pwm, 1); +static SENSOR_DEVICE_ATTR(pwm2_enable, S_IRUGO | S_IWUSR, + show_pwm_enable, set_pwm_enable, 1); +static SENSOR_DEVICE_ATTR(pwm2_freq, S_IRUGO | S_IWUSR, + show_pwm_freq, set_pwm_freq, 1); +static SENSOR_DEVICE_ATTR(pwm2_mode, S_IRUGO, show_pwm_mode, NULL, 1); +static SENSOR_DEVICE_ATTR(pwm3, S_IRUGO, show_pwm, set_pwm, 2); +static SENSOR_DEVICE_ATTR(pwm3_enable, S_IRUGO | S_IWUSR, + show_pwm_enable, set_pwm_enable, 2); +static SENSOR_DEVICE_ATTR(pwm3_freq, S_IRUGO | S_IWUSR, + show_pwm_freq, set_pwm_freq, 2); +static SENSOR_DEVICE_ATTR(pwm3_mode, S_IRUGO, show_pwm_mode, NULL, 2); + static SENSOR_DEVICE_ATTR(in0_alarm, S_IRUGO, show_alarm, NULL, 0); static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, show_alarm, NULL, 1); static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, show_alarm, NULL, 2); @@ -676,6 +949,8 @@ static SENSOR_DEVICE_ATTR(in5_alarm, S_IRUGO, show_alarm, NULL, 5); static SENSOR_DEVICE_ATTR(in6_alarm, S_IRUGO, show_alarm, NULL, 6); static SENSOR_DEVICE_ATTR(in7_alarm, S_IRUGO, show_alarm, NULL, 7); static SENSOR_DEVICE_ATTR(in8_alarm, S_IRUGO, show_alarm, NULL, 8); +static SENSOR_DEVICE_ATTR(in9_alarm, S_IRUGO, show_alarm, NULL, 9); +static SENSOR_DEVICE_ATTR(in10_alarm, S_IRUGO, show_alarm, NULL, 10); static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 11); static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL, 12); static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO, show_alarm, NULL, 13); @@ -689,9 +964,9 @@ static DEVICE_ATTR(alarms_temp, S_IRUGO, show_alarms_temp, NULL); static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); static struct attribute *f71805f_attributes[] = { - &dev_attr_in0_input.attr, - &dev_attr_in0_max.attr, - &dev_attr_in0_min.attr, + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in0_max.dev_attr.attr, + &sensor_dev_attr_in0_min.dev_attr.attr, &sensor_dev_attr_in1_input.dev_attr.attr, &sensor_dev_attr_in1_max.dev_attr.attr, &sensor_dev_attr_in1_min.dev_attr.attr, @@ -701,9 +976,6 @@ static struct attribute *f71805f_attributes[] = { &sensor_dev_attr_in3_input.dev_attr.attr, &sensor_dev_attr_in3_max.dev_attr.attr, &sensor_dev_attr_in3_min.dev_attr.attr, - &sensor_dev_attr_in4_input.dev_attr.attr, - &sensor_dev_attr_in4_max.dev_attr.attr, - &sensor_dev_attr_in4_min.dev_attr.attr, &sensor_dev_attr_in5_input.dev_attr.attr, &sensor_dev_attr_in5_max.dev_attr.attr, &sensor_dev_attr_in5_min.dev_attr.attr, @@ -713,9 +985,29 @@ static struct attribute *f71805f_attributes[] = { &sensor_dev_attr_in7_input.dev_attr.attr, &sensor_dev_attr_in7_max.dev_attr.attr, &sensor_dev_attr_in7_min.dev_attr.attr, - &sensor_dev_attr_in8_input.dev_attr.attr, - &sensor_dev_attr_in8_max.dev_attr.attr, - &sensor_dev_attr_in8_min.dev_attr.attr, + + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan1_alarm.dev_attr.attr, + &sensor_dev_attr_fan1_target.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan2_alarm.dev_attr.attr, + &sensor_dev_attr_fan2_target.dev_attr.attr, + &sensor_dev_attr_fan3_input.dev_attr.attr, + &sensor_dev_attr_fan3_min.dev_attr.attr, + &sensor_dev_attr_fan3_alarm.dev_attr.attr, + &sensor_dev_attr_fan3_target.dev_attr.attr, + + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_pwm1_enable.dev_attr.attr, + &sensor_dev_attr_pwm1_mode.dev_attr.attr, + &sensor_dev_attr_pwm2.dev_attr.attr, + &sensor_dev_attr_pwm2_enable.dev_attr.attr, + &sensor_dev_attr_pwm2_mode.dev_attr.attr, + &sensor_dev_attr_pwm3.dev_attr.attr, + &sensor_dev_attr_pwm3_enable.dev_attr.attr, + &sensor_dev_attr_pwm3_mode.dev_attr.attr, &sensor_dev_attr_temp1_input.dev_attr.attr, &sensor_dev_attr_temp1_max.dev_attr.attr, @@ -734,11 +1026,9 @@ static struct attribute *f71805f_attributes[] = { &sensor_dev_attr_in1_alarm.dev_attr.attr, &sensor_dev_attr_in2_alarm.dev_attr.attr, &sensor_dev_attr_in3_alarm.dev_attr.attr, - &sensor_dev_attr_in4_alarm.dev_attr.attr, &sensor_dev_attr_in5_alarm.dev_attr.attr, &sensor_dev_attr_in6_alarm.dev_attr.attr, &sensor_dev_attr_in7_alarm.dev_attr.attr, - &sensor_dev_attr_in8_alarm.dev_attr.attr, &dev_attr_alarms_in.attr, &sensor_dev_attr_temp1_alarm.dev_attr.attr, &sensor_dev_attr_temp2_alarm.dev_attr.attr, @@ -754,29 +1044,59 @@ static const struct attribute_group f71805f_group = { .attrs = f71805f_attributes, }; -static struct attribute *f71805f_attributes_fan[3][4] = { +static struct attribute *f71805f_attributes_optin[4][5] = { { - &sensor_dev_attr_fan1_input.dev_attr.attr, - &sensor_dev_attr_fan1_min.dev_attr.attr, - &sensor_dev_attr_fan1_alarm.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in4_max.dev_attr.attr, + &sensor_dev_attr_in4_min.dev_attr.attr, + &sensor_dev_attr_in4_alarm.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_in8_input.dev_attr.attr, + &sensor_dev_attr_in8_max.dev_attr.attr, + &sensor_dev_attr_in8_min.dev_attr.attr, + &sensor_dev_attr_in8_alarm.dev_attr.attr, NULL }, { - &sensor_dev_attr_fan2_input.dev_attr.attr, - &sensor_dev_attr_fan2_min.dev_attr.attr, - &sensor_dev_attr_fan2_alarm.dev_attr.attr, + &sensor_dev_attr_in9_input.dev_attr.attr, + &sensor_dev_attr_in9_max.dev_attr.attr, + &sensor_dev_attr_in9_min.dev_attr.attr, + &sensor_dev_attr_in9_alarm.dev_attr.attr, NULL }, { - &sensor_dev_attr_fan3_input.dev_attr.attr, - &sensor_dev_attr_fan3_min.dev_attr.attr, - &sensor_dev_attr_fan3_alarm.dev_attr.attr, + &sensor_dev_attr_in10_input.dev_attr.attr, + &sensor_dev_attr_in10_max.dev_attr.attr, + &sensor_dev_attr_in10_min.dev_attr.attr, + &sensor_dev_attr_in10_alarm.dev_attr.attr, NULL } }; -static const struct attribute_group f71805f_group_fan[3] = { - { .attrs = f71805f_attributes_fan[0] }, - { .attrs = f71805f_attributes_fan[1] }, - { .attrs = f71805f_attributes_fan[2] }, +static const struct attribute_group f71805f_group_optin[4] = { + { .attrs = f71805f_attributes_optin[0] }, + { .attrs = f71805f_attributes_optin[1] }, + { .attrs = f71805f_attributes_optin[2] }, + { .attrs = f71805f_attributes_optin[3] }, +}; + +/* We don't include pwm_freq files in the arrays above, because they must be + created conditionally (only if pwm_mode is 1 == PWM) */ +static struct attribute *f71805f_attributes_pwm_freq[] = { + &sensor_dev_attr_pwm1_freq.dev_attr.attr, + &sensor_dev_attr_pwm2_freq.dev_attr.attr, + &sensor_dev_attr_pwm3_freq.dev_attr.attr, + NULL +}; + +static const struct attribute_group f71805f_group_pwm_freq = { + .attrs = f71805f_attributes_pwm_freq, +}; + +/* We also need an indexed access to pwmN files to toggle writability */ +static struct attribute *f71805f_attr_pwm[] = { + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_pwm2.dev_attr.attr, + &sensor_dev_attr_pwm3.dev_attr.attr, }; /* @@ -798,18 +1118,30 @@ static void __devinit f71805f_init_device(struct f71805f_data *data) /* Fan monitoring can be disabled. If it is, we won't be polling the register values, and won't create the related sysfs files. */ for (i = 0; i < 3; i++) { - reg = f71805f_read8(data, F71805F_REG_FAN_CTRL(i)); - if (!(reg & 0x80)) - data->fan_enabled |= (1 << i); + data->fan_ctrl[i] = f71805f_read8(data, + F71805F_REG_FAN_CTRL(i)); + /* Clear latch full bit, else "speed mode" fan speed control + doesn't work */ + if (data->fan_ctrl[i] & FAN_CTRL_LATCH_FULL) { + data->fan_ctrl[i] &= ~FAN_CTRL_LATCH_FULL; + f71805f_write8(data, F71805F_REG_FAN_CTRL(i), + data->fan_ctrl[i]); + } } } static int __devinit f71805f_probe(struct platform_device *pdev) { + struct f71805f_sio_data *sio_data = pdev->dev.platform_data; struct f71805f_data *data; struct resource *res; int i, err; + static const char *names[] = { + "f71805f", + "f71872f", + }; + if (!(data = kzalloc(sizeof(struct f71805f_data), GFP_KERNEL))) { err = -ENOMEM; printk(KERN_ERR DRVNAME ": Out of memory\n"); @@ -819,24 +1151,69 @@ static int __devinit f71805f_probe(struct platform_device *pdev) res = platform_get_resource(pdev, IORESOURCE_IO, 0); data->addr = res->start; mutex_init(&data->lock); - data->name = "f71805f"; + data->name = names[sio_data->kind]; mutex_init(&data->update_lock); platform_set_drvdata(pdev, data); + /* Some voltage inputs depend on chip model and configuration */ + switch (sio_data->kind) { + case f71805f: + data->has_in = 0x1ff; + break; + case f71872f: + data->has_in = 0x6ef; + if (sio_data->fnsel1 & 0x01) + data->has_in |= (1 << 4); /* in4 */ + if (sio_data->fnsel1 & 0x02) + data->has_in |= (1 << 8); /* in8 */ + break; + } + /* Initialize the F71805F chip */ f71805f_init_device(data); /* Register sysfs interface files */ if ((err = sysfs_create_group(&pdev->dev.kobj, &f71805f_group))) goto exit_free; - for (i = 0; i < 3; i++) { - if (!(data->fan_enabled & (1 << i))) - continue; + if (data->has_in & (1 << 4)) { /* in4 */ + if ((err = sysfs_create_group(&pdev->dev.kobj, + &f71805f_group_optin[0]))) + goto exit_remove_files; + } + if (data->has_in & (1 << 8)) { /* in8 */ + if ((err = sysfs_create_group(&pdev->dev.kobj, + &f71805f_group_optin[1]))) + goto exit_remove_files; + } + if (data->has_in & (1 << 9)) { /* in9 (F71872F/FG only) */ if ((err = sysfs_create_group(&pdev->dev.kobj, - &f71805f_group_fan[i]))) + &f71805f_group_optin[2]))) goto exit_remove_files; } + if (data->has_in & (1 << 10)) { /* in9 (F71872F/FG only) */ + if ((err = sysfs_create_group(&pdev->dev.kobj, + &f71805f_group_optin[3]))) + goto exit_remove_files; + } + for (i = 0; i < 3; i++) { + /* If control mode is PWM, create pwm_freq file */ + if (!(data->fan_ctrl[i] & FAN_CTRL_DC_MODE)) { + if ((err = sysfs_create_file(&pdev->dev.kobj, + f71805f_attributes_pwm_freq[i]))) + goto exit_remove_files; + } + /* If PWM is in manual mode, add write permission */ + if (data->fan_ctrl[i] & FAN_CTRL_MODE_MANUAL) { + if ((err = sysfs_chmod_file(&pdev->dev.kobj, + f71805f_attr_pwm[i], + S_IRUGO | S_IWUSR))) { + dev_err(&pdev->dev, "chmod +w pwm%d failed\n", + i + 1); + goto exit_remove_files; + } + } + } data->class_dev = hwmon_device_register(&pdev->dev); if (IS_ERR(data->class_dev)) { @@ -849,8 +1226,9 @@ static int __devinit f71805f_probe(struct platform_device *pdev) exit_remove_files: sysfs_remove_group(&pdev->dev.kobj, &f71805f_group); - for (i = 0; i < 3; i++) - sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_fan[i]); + for (i = 0; i < 4; i++) + sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_optin[i]); + sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_pwm_freq); exit_free: platform_set_drvdata(pdev, NULL); kfree(data); @@ -866,8 +1244,9 @@ static int __devexit f71805f_remove(struct platform_device *pdev) platform_set_drvdata(pdev, NULL); hwmon_device_unregister(data->class_dev); sysfs_remove_group(&pdev->dev.kobj, &f71805f_group); - for (i = 0; i < 3; i++) - sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_fan[i]); + for (i = 0; i < 4; i++) + sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_optin[i]); + sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_pwm_freq); kfree(data); return 0; @@ -882,7 +1261,8 @@ static struct platform_driver f71805f_driver = { .remove = __devexit_p(f71805f_remove), }; -static int __init f71805f_device_add(unsigned short address) +static int __init f71805f_device_add(unsigned short address, + const struct f71805f_sio_data *sio_data) { struct resource res = { .start = address, @@ -906,26 +1286,45 @@ static int __init f71805f_device_add(unsigned short address) goto exit_device_put; } + pdev->dev.platform_data = kmalloc(sizeof(struct f71805f_sio_data), + GFP_KERNEL); + if (!pdev->dev.platform_data) { + err = -ENOMEM; + printk(KERN_ERR DRVNAME ": Platform data allocation failed\n"); + goto exit_device_put; + } + memcpy(pdev->dev.platform_data, sio_data, + sizeof(struct f71805f_sio_data)); + err = platform_device_add(pdev); if (err) { printk(KERN_ERR DRVNAME ": Device addition failed (%d)\n", err); - goto exit_device_put; + goto exit_kfree_data; } return 0; +exit_kfree_data: + kfree(pdev->dev.platform_data); + pdev->dev.platform_data = NULL; exit_device_put: platform_device_put(pdev); exit: return err; } -static int __init f71805f_find(int sioaddr, unsigned short *address) +static int __init f71805f_find(int sioaddr, unsigned short *address, + struct f71805f_sio_data *sio_data) { int err = -ENODEV; u16 devid; + static const char *names[] = { + "F71805F/FG", + "F71872F/FG", + }; + superio_enter(sioaddr); devid = superio_inw(sioaddr, SIO_REG_MANID); @@ -933,7 +1332,15 @@ static int __init f71805f_find(int sioaddr, unsigned short *address) goto exit; devid = superio_inw(sioaddr, SIO_REG_DEVID); - if (devid != SIO_F71805F_ID) { + switch (devid) { + case SIO_F71805F_ID: + sio_data->kind = f71805f; + break; + case SIO_F71872F_ID: + sio_data->kind = f71872f; + sio_data->fnsel1 = superio_inb(sioaddr, SIO_REG_FNSEL1); + break; + default: printk(KERN_INFO DRVNAME ": Unsupported Fintek device, " "skipping\n"); goto exit; @@ -952,10 +1359,12 @@ static int __init f71805f_find(int sioaddr, unsigned short *address) "skipping\n"); goto exit; } + *address &= ~(REGION_LENGTH - 1); /* Ignore 3 LSB */ err = 0; - printk(KERN_INFO DRVNAME ": Found F71805F chip at %#x, revision %u\n", - *address, superio_inb(sioaddr, SIO_REG_DEVREV)); + printk(KERN_INFO DRVNAME ": Found %s chip at %#x, revision %u\n", + names[sio_data->kind], *address, + superio_inb(sioaddr, SIO_REG_DEVREV)); exit: superio_exit(sioaddr); @@ -966,9 +1375,10 @@ static int __init f71805f_init(void) { int err; unsigned short address; + struct f71805f_sio_data sio_data; - if (f71805f_find(0x2e, &address) - && f71805f_find(0x4e, &address)) + if (f71805f_find(0x2e, &address, &sio_data) + && f71805f_find(0x4e, &address, &sio_data)) return -ENODEV; err = platform_driver_register(&f71805f_driver); @@ -976,7 +1386,7 @@ static int __init f71805f_init(void) goto exit; /* Sets global pdev as a side effect */ - err = f71805f_device_add(address); + err = f71805f_device_add(address, &sio_data); if (err) goto exit_driver; @@ -990,13 +1400,16 @@ exit: static void __exit f71805f_exit(void) { + kfree(pdev->dev.platform_data); + pdev->dev.platform_data = NULL; platform_device_unregister(pdev); + platform_driver_unregister(&f71805f_driver); } MODULE_AUTHOR("Jean Delvare <khali@linux-fr>"); MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("F71805F hardware monitoring driver"); +MODULE_DESCRIPTION("F71805F/F71872F hardware monitoring driver"); module_init(f71805f_init); module_exit(f71805f_exit); diff --git a/drivers/hwmon/hdaps.c b/drivers/hwmon/hdaps.c index e8ef62b83d6b..bf759ea545ac 100644 --- a/drivers/hwmon/hdaps.c +++ b/drivers/hwmon/hdaps.c @@ -478,74 +478,64 @@ static struct attribute_group hdaps_attribute_group = { /* Module stuff */ /* hdaps_dmi_match - found a match. return one, short-circuiting the hunt. */ -static int hdaps_dmi_match(struct dmi_system_id *id) +static int __init hdaps_dmi_match(struct dmi_system_id *id) { printk(KERN_INFO "hdaps: %s detected.\n", id->ident); return 1; } /* hdaps_dmi_match_invert - found an inverted match. */ -static int hdaps_dmi_match_invert(struct dmi_system_id *id) +static int __init hdaps_dmi_match_invert(struct dmi_system_id *id) { hdaps_invert = 1; printk(KERN_INFO "hdaps: inverting axis readings.\n"); return hdaps_dmi_match(id); } -#define HDAPS_DMI_MATCH_NORMAL(model) { \ - .ident = "IBM " model, \ +#define HDAPS_DMI_MATCH_NORMAL(vendor, model) { \ + .ident = vendor " " model, \ .callback = hdaps_dmi_match, \ .matches = { \ - DMI_MATCH(DMI_BOARD_VENDOR, "IBM"), \ + DMI_MATCH(DMI_BOARD_VENDOR, vendor), \ DMI_MATCH(DMI_PRODUCT_VERSION, model) \ } \ } -#define HDAPS_DMI_MATCH_INVERT(model) { \ - .ident = "IBM " model, \ +#define HDAPS_DMI_MATCH_INVERT(vendor, model) { \ + .ident = vendor " " model, \ .callback = hdaps_dmi_match_invert, \ .matches = { \ - DMI_MATCH(DMI_BOARD_VENDOR, "IBM"), \ + DMI_MATCH(DMI_BOARD_VENDOR, vendor), \ DMI_MATCH(DMI_PRODUCT_VERSION, model) \ } \ } -#define HDAPS_DMI_MATCH_LENOVO(model) { \ - .ident = "Lenovo " model, \ - .callback = hdaps_dmi_match_invert, \ - .matches = { \ - DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), \ - DMI_MATCH(DMI_PRODUCT_VERSION, model) \ - } \ -} +/* Note that HDAPS_DMI_MATCH_NORMAL("ThinkPad T42") would match + "ThinkPad T42p", so the order of the entries matters. + If your ThinkPad is not recognized, please update to latest + BIOS. This is especially the case for some R52 ThinkPads. */ +static struct dmi_system_id __initdata hdaps_whitelist[] = { + HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad R50p"), + HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R50"), + HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R51"), + HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R52"), + HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T41p"), + HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T41"), + HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T42p"), + HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T42"), + HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T43"), + HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T60"), + HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad X40"), + HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad X41"), + HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60"), + HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad Z60m"), + { .ident = NULL } +}; static int __init hdaps_init(void) { int ret; - /* Note that HDAPS_DMI_MATCH_NORMAL("ThinkPad T42") would match - "ThinkPad T42p", so the order of the entries matters */ - struct dmi_system_id hdaps_whitelist[] = { - HDAPS_DMI_MATCH_NORMAL("ThinkPad H"), - HDAPS_DMI_MATCH_INVERT("ThinkPad R50p"), - HDAPS_DMI_MATCH_NORMAL("ThinkPad R50"), - HDAPS_DMI_MATCH_NORMAL("ThinkPad R51"), - HDAPS_DMI_MATCH_NORMAL("ThinkPad R52"), - HDAPS_DMI_MATCH_NORMAL("ThinkPad H"), /* R52 (1846AQG) */ - HDAPS_DMI_MATCH_INVERT("ThinkPad T41p"), - HDAPS_DMI_MATCH_NORMAL("ThinkPad T41"), - HDAPS_DMI_MATCH_INVERT("ThinkPad T42p"), - HDAPS_DMI_MATCH_NORMAL("ThinkPad T42"), - HDAPS_DMI_MATCH_NORMAL("ThinkPad T43"), - HDAPS_DMI_MATCH_LENOVO("ThinkPad T60p"), - HDAPS_DMI_MATCH_LENOVO("ThinkPad T60"), - HDAPS_DMI_MATCH_NORMAL("ThinkPad X40"), - HDAPS_DMI_MATCH_NORMAL("ThinkPad X41"), - HDAPS_DMI_MATCH_LENOVO("ThinkPad X60"), - HDAPS_DMI_MATCH_NORMAL("ThinkPad Z60m"), - { .ident = NULL } - }; - if (!dmi_check_system(hdaps_whitelist)) { printk(KERN_WARNING "hdaps: supported laptop not found!\n"); ret = -ENODEV; diff --git a/drivers/hwmon/hwmon-vid.c b/drivers/hwmon/hwmon-vid.c index 9d67320e6840..31c42002708f 100644 --- a/drivers/hwmon/hwmon-vid.c +++ b/drivers/hwmon/hwmon-vid.c @@ -1,7 +1,7 @@ /* hwmon-vid.c - VID/VRM/VRD voltage conversions - Copyright (c) 2004 Rudolf Marek <r.marek@sh.cvut.cz> + Copyright (c) 2004 Rudolf Marek <r.marek@assembler.cz> Partly imported from i2c-vid.h of the lm_sensors project Copyright (c) 2002 Mark D. Studebaker <mdsxyz123@yahoo.com> @@ -232,7 +232,7 @@ u8 vid_which_vrm(void) EXPORT_SYMBOL(vid_from_reg); EXPORT_SYMBOL(vid_which_vrm); -MODULE_AUTHOR("Rudolf Marek <r.marek@sh.cvut.cz>"); +MODULE_AUTHOR("Rudolf Marek <r.marek@assembler.cz>"); MODULE_DESCRIPTION("hwmon-vid driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/it87.c b/drivers/hwmon/it87.c index 323ef06719c1..1ed8b7e2c35d 100644 --- a/drivers/hwmon/it87.c +++ b/drivers/hwmon/it87.c @@ -3,7 +3,7 @@ monitoring. Supports: IT8705F Super I/O chip w/LPC interface - IT8712F Super I/O chip w/LPC interface & SMBus + IT8712F Super I/O chip w/LPC interface IT8716F Super I/O chip w/LPC interface IT8718F Super I/O chip w/LPC interface Sis950 A clone of the IT8705F @@ -41,12 +41,8 @@ #include <asm/io.h> -/* Addresses to scan */ -static unsigned short normal_i2c[] = { 0x2d, I2C_CLIENT_END }; static unsigned short isa_address; - -/* Insmod parameters */ -I2C_CLIENT_INSMOD_4(it87, it8712, it8716, it8718); +enum chips { it87, it8712, it8716, it8718 }; #define REG 0x2e /* The register to read/write */ #define DEV 0x07 /* Register: Logical device select */ @@ -162,8 +158,6 @@ static u8 vid_value; #define IT87_REG_TEMP_HIGH(nr) (0x40 + (nr) * 2) #define IT87_REG_TEMP_LOW(nr) (0x41 + (nr) * 2) -#define IT87_REG_I2C_ADDR 0x48 - #define IT87_REG_VIN_ENABLE 0x50 #define IT87_REG_TEMP_ENABLE 0x51 @@ -242,33 +236,22 @@ struct it87_data { }; -static int it87_attach_adapter(struct i2c_adapter *adapter); -static int it87_isa_attach_adapter(struct i2c_adapter *adapter); -static int it87_detect(struct i2c_adapter *adapter, int address, int kind); +static int it87_detect(struct i2c_adapter *adapter); static int it87_detach_client(struct i2c_client *client); static int it87_read_value(struct i2c_client *client, u8 reg); -static int it87_write_value(struct i2c_client *client, u8 reg, u8 value); +static void it87_write_value(struct i2c_client *client, u8 reg, u8 value); static struct it87_data *it87_update_device(struct device *dev); static int it87_check_pwm(struct i2c_client *client); static void it87_init_client(struct i2c_client *client, struct it87_data *data); -static struct i2c_driver it87_driver = { - .driver = { - .name = "it87", - }, - .id = I2C_DRIVERID_IT87, - .attach_adapter = it87_attach_adapter, - .detach_client = it87_detach_client, -}; - static struct i2c_driver it87_isa_driver = { .driver = { .owner = THIS_MODULE, .name = "it87-isa", }, - .attach_adapter = it87_isa_attach_adapter, + .attach_adapter = it87_detect, .detach_client = it87_detach_client, }; @@ -850,22 +833,6 @@ static const struct attribute_group it87_group_opt = { .attrs = it87_attributes_opt, }; -/* This function is called when: - * it87_driver is inserted (when this module is loaded), for each - available adapter - * when a new adapter is inserted (and it87_driver is still present) */ -static int it87_attach_adapter(struct i2c_adapter *adapter) -{ - if (!(adapter->class & I2C_CLASS_HWMON)) - return 0; - return i2c_probe(adapter, &addr_data, it87_detect); -} - -static int it87_isa_attach_adapter(struct i2c_adapter *adapter) -{ - return it87_detect(adapter, isa_address, -1); -} - /* SuperIO detection - will change isa_address if a chip is found */ static int __init it87_find(unsigned short *address) { @@ -916,29 +883,20 @@ exit: } /* This function is called by i2c_probe */ -static int it87_detect(struct i2c_adapter *adapter, int address, int kind) +static int it87_detect(struct i2c_adapter *adapter) { - int i; struct i2c_client *new_client; struct it87_data *data; int err = 0; - const char *name = ""; - int is_isa = i2c_is_isa_adapter(adapter); + const char *name; int enable_pwm_interface; - if (!is_isa && - !i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) - goto ERROR0; - /* Reserve the ISA region */ - if (is_isa) - if (!request_region(address, IT87_EXTENT, - it87_isa_driver.driver.name)) - goto ERROR0; - - /* For now, we presume we have a valid client. We create the - client structure, even though we cannot fill it completely yet. - But it allows us to access it87_{read,write}_value. */ + if (!request_region(isa_address, IT87_EXTENT, + it87_isa_driver.driver.name)){ + err = -EBUSY; + goto ERROR0; + } if (!(data = kzalloc(sizeof(struct it87_data), GFP_KERNEL))) { err = -ENOMEM; @@ -946,80 +904,46 @@ static int it87_detect(struct i2c_adapter *adapter, int address, int kind) } new_client = &data->client; - if (is_isa) - mutex_init(&data->lock); + mutex_init(&data->lock); i2c_set_clientdata(new_client, data); - new_client->addr = address; + new_client->addr = isa_address; new_client->adapter = adapter; - new_client->driver = is_isa ? &it87_isa_driver : &it87_driver; - new_client->flags = 0; + new_client->driver = &it87_isa_driver; /* Now, we do the remaining detection. */ - - if (kind < 0) { - if ((it87_read_value(new_client, IT87_REG_CONFIG) & 0x80) - || (!is_isa - && it87_read_value(new_client, IT87_REG_I2C_ADDR) != address)) { - err = -ENODEV; - goto ERROR2; - } + if ((it87_read_value(new_client, IT87_REG_CONFIG) & 0x80) + || it87_read_value(new_client, IT87_REG_CHIPID) != 0x90) { + err = -ENODEV; + goto ERROR2; } /* Determine the chip type. */ - if (kind <= 0) { - i = it87_read_value(new_client, IT87_REG_CHIPID); - if (i == 0x90) { - kind = it87; - if (is_isa) { - switch (chip_type) { - case IT8712F_DEVID: - kind = it8712; - break; - case IT8716F_DEVID: - kind = it8716; - break; - case IT8718F_DEVID: - kind = it8718; - break; - } - } - } - else { - if (kind == 0) - dev_info(&adapter->dev, - "Ignoring 'force' parameter for unknown chip at " - "adapter %d, address 0x%02x\n", - i2c_adapter_id(adapter), address); - err = -ENODEV; - goto ERROR2; - } - } - - if (kind == it87) { - name = "it87"; - } else if (kind == it8712) { + switch (chip_type) { + case IT8712F_DEVID: + data->type = it8712; name = "it8712"; - } else if (kind == it8716) { + break; + case IT8716F_DEVID: + data->type = it8716; name = "it8716"; - } else if (kind == it8718) { + break; + case IT8718F_DEVID: + data->type = it8718; name = "it8718"; + break; + default: + data->type = it87; + name = "it87"; } /* Fill in the remaining client fields and put it into the global list */ strlcpy(new_client->name, name, I2C_NAME_SIZE); - data->type = kind; - data->valid = 0; mutex_init(&data->update_lock); /* Tell the I2C layer a new client has arrived */ if ((err = i2c_attach_client(new_client))) goto ERROR2; - if (!is_isa) - dev_info(&new_client->dev, "The I2C interface to IT87xxF " - "hardware monitoring chips is deprecated. Please " - "report if you still rely on it.\n"); - /* Check PWM configuration */ enable_pwm_interface = it87_check_pwm(new_client); @@ -1129,8 +1053,7 @@ ERROR3: ERROR2: kfree(data); ERROR1: - if (is_isa) - release_region(address, IT87_EXTENT); + release_region(isa_address, IT87_EXTENT); ERROR0: return err; } @@ -1147,50 +1070,39 @@ static int it87_detach_client(struct i2c_client *client) if ((err = i2c_detach_client(client))) return err; - if(i2c_is_isa_client(client)) - release_region(client->addr, IT87_EXTENT); + release_region(client->addr, IT87_EXTENT); kfree(data); return 0; } -/* 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 - separately. +/* ISA access must be locked explicitly! We ignore the IT87 BUSY flag at this moment - it could lead to deadlocks, would slow down the IT87 access and should not be necessary. */ static int it87_read_value(struct i2c_client *client, u8 reg) { struct it87_data *data = i2c_get_clientdata(client); - int res; - if (i2c_is_isa_client(client)) { - mutex_lock(&data->lock); - outb_p(reg, client->addr + IT87_ADDR_REG_OFFSET); - res = inb_p(client->addr + IT87_DATA_REG_OFFSET); - mutex_unlock(&data->lock); - return res; - } else - return i2c_smbus_read_byte_data(client, reg); + + mutex_lock(&data->lock); + outb_p(reg, client->addr + IT87_ADDR_REG_OFFSET); + res = inb_p(client->addr + IT87_DATA_REG_OFFSET); + mutex_unlock(&data->lock); + + return res; } -/* The SMBus locks itself, but ISA access muse be locked explicitly! - We don't want to lock the whole ISA bus, so we lock each client - separately. +/* ISA access must be locked explicitly! We ignore the IT87 BUSY flag at this moment - it could lead to deadlocks, would slow down the IT87 access and should not be necessary. */ -static int it87_write_value(struct i2c_client *client, u8 reg, u8 value) +static void it87_write_value(struct i2c_client *client, u8 reg, u8 value) { struct it87_data *data = i2c_get_clientdata(client); - if (i2c_is_isa_client(client)) { - mutex_lock(&data->lock); - outb_p(reg, client->addr + IT87_ADDR_REG_OFFSET); - outb_p(value, client->addr + IT87_DATA_REG_OFFSET); - mutex_unlock(&data->lock); - return 0; - } else - return i2c_smbus_write_byte_data(client, reg, value); + mutex_lock(&data->lock); + outb_p(reg, client->addr + IT87_ADDR_REG_OFFSET); + outb_p(value, client->addr + IT87_DATA_REG_OFFSET); + mutex_unlock(&data->lock); } /* Return 1 if and only if the PWM interface is safe to use */ @@ -1426,26 +1338,14 @@ static int __init sm_it87_init(void) { int res; - res = i2c_add_driver(&it87_driver); - if (res) + if ((res = it87_find(&isa_address))) return res; - - if (!it87_find(&isa_address)) { - res = i2c_isa_add_driver(&it87_isa_driver); - if (res) { - i2c_del_driver(&it87_driver); - return res; - } - } - - return 0; + return i2c_isa_add_driver(&it87_isa_driver); } static void __exit sm_it87_exit(void) { - if (isa_address) - i2c_isa_del_driver(&it87_isa_driver); - i2c_del_driver(&it87_driver); + i2c_isa_del_driver(&it87_isa_driver); } diff --git a/drivers/hwmon/k8temp.c b/drivers/hwmon/k8temp.c index f58b64ed09e3..5d8d0ca08fa9 100644 --- a/drivers/hwmon/k8temp.c +++ b/drivers/hwmon/k8temp.c @@ -1,7 +1,7 @@ /* * k8temp.c - Linux kernel module for hardware monitoring * - * Copyright (C) 2006 Rudolf Marek <r.marek@sh.cvut.cz> + * Copyright (C) 2006 Rudolf Marek <r.marek@assembler.cz> * * Inspired from the w83785 and amd756 drivers. * @@ -286,7 +286,7 @@ static void __exit k8temp_exit(void) pci_unregister_driver(&k8temp_driver); } -MODULE_AUTHOR("Rudolf Marek <r.marek@sh.cvut.cz>"); +MODULE_AUTHOR("Rudolf Marek <r.marek@assembler.cz>"); MODULE_DESCRIPTION("AMD K8 core temperature monitor"); MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pc87360.c b/drivers/hwmon/pc87360.c index 3b8b81984ad4..c8a21be09d87 100644 --- a/drivers/hwmon/pc87360.c +++ b/drivers/hwmon/pc87360.c @@ -1000,7 +1000,7 @@ static int pc87360_detect(struct i2c_adapter *adapter) (i&0x02) ? "external" : "internal"); data->vid_conf = confreg[3]; - data->vrm = 90; + data->vrm = vid_which_vrm(); } /* Fan clock dividers may be needed before any data is read */ diff --git a/drivers/hwmon/pc87427.c b/drivers/hwmon/pc87427.c new file mode 100644 index 000000000000..affa21a5ccfd --- /dev/null +++ b/drivers/hwmon/pc87427.c @@ -0,0 +1,627 @@ +/* + * pc87427.c - hardware monitoring driver for the + * National Semiconductor PC87427 Super-I/O chip + * Copyright (C) 2006 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 version 2 as + * published by the Free Software Foundation. + * + * 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. + * + * Supports the following chips: + * + * Chip #vin #fan #pwm #temp devid + * PC87427 - 8 - - 0xF2 + * + * This driver assumes that no more than one chip is present. + * Only fan inputs are supported so far, although the chip can do much more. + */ + +#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 <linux/sysfs.h> +#include <asm/io.h> + +static struct platform_device *pdev; + +#define DRVNAME "pc87427" + +/* The lock mutex protects both the I/O accesses (needed because the + device is using banked registers) and the register cache (needed to keep + the data in the registers and the cache in sync at any time). */ +struct pc87427_data { + struct class_device *class_dev; + struct mutex lock; + int address[2]; + const char *name; + + unsigned long last_updated; /* in jiffies */ + u8 fan_enabled; /* bit vector */ + u16 fan[8]; /* register values */ + u16 fan_min[8]; /* register values */ + u8 fan_status[8]; /* register values */ +}; + +/* + * Super-I/O registers and operations + */ + +#define SIOREG_LDSEL 0x07 /* Logical device select */ +#define SIOREG_DEVID 0x20 /* Device ID */ +#define SIOREG_ACT 0x30 /* Device activation */ +#define SIOREG_MAP 0x50 /* I/O or memory mapping */ +#define SIOREG_IOBASE 0x60 /* I/O base address */ + +static const u8 logdev[2] = { 0x09, 0x14 }; +static const char *logdev_str[2] = { DRVNAME " FMC", DRVNAME " HMC" }; +#define LD_FAN 0 +#define LD_IN 1 +#define LD_TEMP 1 + +static inline void superio_outb(int sioaddr, int reg, int val) +{ + outb(reg, sioaddr); + outb(val, sioaddr + 1); +} + +static inline int superio_inb(int sioaddr, int reg) +{ + outb(reg, sioaddr); + return inb(sioaddr + 1); +} + +static inline void superio_exit(int sioaddr) +{ + outb(0x02, sioaddr); + outb(0x02, sioaddr + 1); +} + +/* + * Logical devices + */ + +#define REGION_LENGTH 32 +#define PC87427_REG_BANK 0x0f +#define BANK_FM(nr) (nr) +#define BANK_FT(nr) (0x08 + (nr)) +#define BANK_FC(nr) (0x10 + (nr) * 2) + +/* + * I/O access functions + */ + +/* ldi is the logical device index */ +static inline int pc87427_read8(struct pc87427_data *data, u8 ldi, u8 reg) +{ + return inb(data->address[ldi] + reg); +} + +/* Must be called with data->lock held, except during init */ +static inline int pc87427_read8_bank(struct pc87427_data *data, u8 ldi, + u8 bank, u8 reg) +{ + outb(bank, data->address[ldi] + PC87427_REG_BANK); + return inb(data->address[ldi] + reg); +} + +/* Must be called with data->lock held, except during init */ +static inline void pc87427_write8_bank(struct pc87427_data *data, u8 ldi, + u8 bank, u8 reg, u8 value) +{ + outb(bank, data->address[ldi] + PC87427_REG_BANK); + outb(value, data->address[ldi] + reg); +} + +/* + * Fan registers and conversions + */ + +/* fan data registers are 16-bit wide */ +#define PC87427_REG_FAN 0x12 +#define PC87427_REG_FAN_MIN 0x14 +#define PC87427_REG_FAN_STATUS 0x10 + +#define FAN_STATUS_STALL (1 << 3) +#define FAN_STATUS_LOSPD (1 << 1) +#define FAN_STATUS_MONEN (1 << 0) + +/* Dedicated function to read all registers related to a given fan input. + This saves us quite a few locks and bank selections. + Must be called with data->lock held. + nr is from 0 to 7 */ +static void pc87427_readall_fan(struct pc87427_data *data, u8 nr) +{ + int iobase = data->address[LD_FAN]; + + outb(BANK_FM(nr), iobase + PC87427_REG_BANK); + data->fan[nr] = inw(iobase + PC87427_REG_FAN); + data->fan_min[nr] = inw(iobase + PC87427_REG_FAN_MIN); + data->fan_status[nr] = inb(iobase + PC87427_REG_FAN_STATUS); + /* Clear fan alarm bits */ + outb(data->fan_status[nr], iobase + PC87427_REG_FAN_STATUS); +} + +/* The 2 LSB of fan speed registers are used for something different. + The actual 2 LSB of the measurements are not available. */ +static inline unsigned long fan_from_reg(u16 reg) +{ + reg &= 0xfffc; + if (reg == 0x0000 || reg == 0xfffc) + return 0; + return 5400000UL / reg; +} + +/* The 2 LSB of the fan speed limit registers are not significant. */ +static inline u16 fan_to_reg(unsigned long val) +{ + if (val < 83UL) + return 0xffff; + if (val >= 1350000UL) + return 0x0004; + return ((1350000UL + val / 2) / val) << 2; +} + +/* + * Data interface + */ + +static struct pc87427_data *pc87427_update_device(struct device *dev) +{ + struct pc87427_data *data = dev_get_drvdata(dev); + int i; + + mutex_lock(&data->lock); + if (!time_after(jiffies, data->last_updated + HZ) + && data->last_updated) + goto done; + + /* Fans */ + for (i = 0; i < 8; i++) { + if (!(data->fan_enabled & (1 << i))) + continue; + pc87427_readall_fan(data, i); + } + data->last_updated = jiffies; + +done: + mutex_unlock(&data->lock); + return data; +} + +static ssize_t show_fan_input(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pc87427_data *data = pc87427_update_device(dev); + int nr = attr->index; + + return sprintf(buf, "%lu\n", fan_from_reg(data->fan[nr])); +} + +static ssize_t show_fan_min(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pc87427_data *data = pc87427_update_device(dev); + int nr = attr->index; + + return sprintf(buf, "%lu\n", fan_from_reg(data->fan_min[nr])); +} + +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 pc87427_data *data = pc87427_update_device(dev); + int nr = attr->index; + + return sprintf(buf, "%d\n", !!(data->fan_status[nr] + & FAN_STATUS_LOSPD)); +} + +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 pc87427_data *data = pc87427_update_device(dev); + int nr = attr->index; + + return sprintf(buf, "%d\n", !!(data->fan_status[nr] + & FAN_STATUS_STALL)); +} + +static ssize_t set_fan_min(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct pc87427_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + unsigned long val = simple_strtoul(buf, NULL, 10); + int iobase = data->address[LD_FAN]; + + mutex_lock(&data->lock); + outb(BANK_FM(nr), iobase + PC87427_REG_BANK); + /* The low speed limit registers are read-only while monitoring + is enabled, so we have to disable monitoring, then change the + limit, and finally enable monitoring again. */ + outb(0, iobase + PC87427_REG_FAN_STATUS); + data->fan_min[nr] = fan_to_reg(val); + outw(data->fan_min[nr], iobase + PC87427_REG_FAN_MIN); + outb(FAN_STATUS_MONEN, iobase + PC87427_REG_FAN_STATUS); + mutex_unlock(&data->lock); + + return count; +} + +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan_input, NULL, 0); +static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan_input, NULL, 1); +static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, show_fan_input, NULL, 2); +static SENSOR_DEVICE_ATTR(fan4_input, S_IRUGO, show_fan_input, NULL, 3); +static SENSOR_DEVICE_ATTR(fan5_input, S_IRUGO, show_fan_input, NULL, 4); +static SENSOR_DEVICE_ATTR(fan6_input, S_IRUGO, show_fan_input, NULL, 5); +static SENSOR_DEVICE_ATTR(fan7_input, S_IRUGO, show_fan_input, NULL, 6); +static SENSOR_DEVICE_ATTR(fan8_input, S_IRUGO, show_fan_input, NULL, 7); + +static SENSOR_DEVICE_ATTR(fan1_min, S_IWUSR | S_IRUGO, + show_fan_min, set_fan_min, 0); +static SENSOR_DEVICE_ATTR(fan2_min, S_IWUSR | S_IRUGO, + show_fan_min, set_fan_min, 1); +static SENSOR_DEVICE_ATTR(fan3_min, S_IWUSR | S_IRUGO, + show_fan_min, set_fan_min, 2); +static SENSOR_DEVICE_ATTR(fan4_min, S_IWUSR | S_IRUGO, + show_fan_min, set_fan_min, 3); +static SENSOR_DEVICE_ATTR(fan5_min, S_IWUSR | S_IRUGO, + show_fan_min, set_fan_min, 4); +static SENSOR_DEVICE_ATTR(fan6_min, S_IWUSR | S_IRUGO, + show_fan_min, set_fan_min, 5); +static SENSOR_DEVICE_ATTR(fan7_min, S_IWUSR | S_IRUGO, + show_fan_min, set_fan_min, 6); +static SENSOR_DEVICE_ATTR(fan8_min, S_IWUSR | S_IRUGO, + show_fan_min, set_fan_min, 7); + +static SENSOR_DEVICE_ATTR(fan1_alarm, S_IRUGO, show_fan_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO, show_fan_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(fan3_alarm, S_IRUGO, show_fan_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(fan4_alarm, S_IRUGO, show_fan_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(fan5_alarm, S_IRUGO, show_fan_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(fan6_alarm, S_IRUGO, show_fan_alarm, NULL, 5); +static SENSOR_DEVICE_ATTR(fan7_alarm, S_IRUGO, show_fan_alarm, NULL, 6); +static SENSOR_DEVICE_ATTR(fan8_alarm, S_IRUGO, show_fan_alarm, NULL, 7); + +static SENSOR_DEVICE_ATTR(fan1_fault, S_IRUGO, show_fan_fault, NULL, 0); +static SENSOR_DEVICE_ATTR(fan2_fault, S_IRUGO, show_fan_fault, NULL, 1); +static SENSOR_DEVICE_ATTR(fan3_fault, S_IRUGO, show_fan_fault, NULL, 2); +static SENSOR_DEVICE_ATTR(fan4_fault, S_IRUGO, show_fan_fault, NULL, 3); +static SENSOR_DEVICE_ATTR(fan5_fault, S_IRUGO, show_fan_fault, NULL, 4); +static SENSOR_DEVICE_ATTR(fan6_fault, S_IRUGO, show_fan_fault, NULL, 5); +static SENSOR_DEVICE_ATTR(fan7_fault, S_IRUGO, show_fan_fault, NULL, 6); +static SENSOR_DEVICE_ATTR(fan8_fault, S_IRUGO, show_fan_fault, NULL, 7); + +static struct attribute *pc87427_attributes_fan[8][5] = { + { + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan1_alarm.dev_attr.attr, + &sensor_dev_attr_fan1_fault.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan2_alarm.dev_attr.attr, + &sensor_dev_attr_fan2_fault.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_fan3_input.dev_attr.attr, + &sensor_dev_attr_fan3_min.dev_attr.attr, + &sensor_dev_attr_fan3_alarm.dev_attr.attr, + &sensor_dev_attr_fan3_fault.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_fan4_input.dev_attr.attr, + &sensor_dev_attr_fan4_min.dev_attr.attr, + &sensor_dev_attr_fan4_alarm.dev_attr.attr, + &sensor_dev_attr_fan4_fault.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_fan5_input.dev_attr.attr, + &sensor_dev_attr_fan5_min.dev_attr.attr, + &sensor_dev_attr_fan5_alarm.dev_attr.attr, + &sensor_dev_attr_fan5_fault.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_fan6_input.dev_attr.attr, + &sensor_dev_attr_fan6_min.dev_attr.attr, + &sensor_dev_attr_fan6_alarm.dev_attr.attr, + &sensor_dev_attr_fan6_fault.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_fan7_input.dev_attr.attr, + &sensor_dev_attr_fan7_min.dev_attr.attr, + &sensor_dev_attr_fan7_alarm.dev_attr.attr, + &sensor_dev_attr_fan7_fault.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_fan8_input.dev_attr.attr, + &sensor_dev_attr_fan8_min.dev_attr.attr, + &sensor_dev_attr_fan8_alarm.dev_attr.attr, + &sensor_dev_attr_fan8_fault.dev_attr.attr, + NULL + } +}; + +static const struct attribute_group pc87427_group_fan[8] = { + { .attrs = pc87427_attributes_fan[0] }, + { .attrs = pc87427_attributes_fan[1] }, + { .attrs = pc87427_attributes_fan[2] }, + { .attrs = pc87427_attributes_fan[3] }, + { .attrs = pc87427_attributes_fan[4] }, + { .attrs = pc87427_attributes_fan[5] }, + { .attrs = pc87427_attributes_fan[6] }, + { .attrs = pc87427_attributes_fan[7] }, +}; + +static ssize_t show_name(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct pc87427_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", data->name); +} +static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); + + +/* + * Device detection, attach and detach + */ + +static void __devinit pc87427_init_device(struct device *dev) +{ + struct pc87427_data *data = dev_get_drvdata(dev); + int i; + u8 reg; + + /* The FMC module should be ready */ + reg = pc87427_read8(data, LD_FAN, PC87427_REG_BANK); + if (!(reg & 0x80)) + dev_warn(dev, "FMC module not ready!\n"); + + /* Check which fans are enabled */ + for (i = 0; i < 8; i++) { + reg = pc87427_read8_bank(data, LD_FAN, BANK_FM(i), + PC87427_REG_FAN_STATUS); + if (reg & FAN_STATUS_MONEN) + data->fan_enabled |= (1 << i); + } + + if (!data->fan_enabled) { + dev_dbg(dev, "Enabling all fan inputs\n"); + for (i = 0; i < 8; i++) + pc87427_write8_bank(data, LD_FAN, BANK_FM(i), + PC87427_REG_FAN_STATUS, + FAN_STATUS_MONEN); + data->fan_enabled = 0xff; + } +} + +static int __devinit pc87427_probe(struct platform_device *pdev) +{ + struct pc87427_data *data; + struct resource *res; + int i, err; + + if (!(data = kzalloc(sizeof(struct pc87427_data), GFP_KERNEL))) { + err = -ENOMEM; + printk(KERN_ERR DRVNAME ": Out of memory\n"); + goto exit; + } + + /* This will need to be revisited when we add support for + temperature and voltage monitoring. */ + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + data->address[0] = res->start; + + mutex_init(&data->lock); + data->name = "pc87427"; + platform_set_drvdata(pdev, data); + pc87427_init_device(&pdev->dev); + + /* Register sysfs hooks */ + if ((err = device_create_file(&pdev->dev, &dev_attr_name))) + goto exit_kfree; + for (i = 0; i < 8; i++) { + if (!(data->fan_enabled & (1 << i))) + continue; + if ((err = sysfs_create_group(&pdev->dev.kobj, + &pc87427_group_fan[i]))) + goto exit_remove_files; + } + + data->class_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(data->class_dev)) { + err = PTR_ERR(data->class_dev); + dev_err(&pdev->dev, "Class registration failed (%d)\n", err); + goto exit_remove_files; + } + + return 0; + +exit_remove_files: + for (i = 0; i < 8; i++) { + if (!(data->fan_enabled & (1 << i))) + continue; + sysfs_remove_group(&pdev->dev.kobj, &pc87427_group_fan[i]); + } +exit_kfree: + platform_set_drvdata(pdev, NULL); + kfree(data); +exit: + return err; +} + +static int __devexit pc87427_remove(struct platform_device *pdev) +{ + struct pc87427_data *data = platform_get_drvdata(pdev); + int i; + + platform_set_drvdata(pdev, NULL); + hwmon_device_unregister(data->class_dev); + device_remove_file(&pdev->dev, &dev_attr_name); + for (i = 0; i < 8; i++) { + if (!(data->fan_enabled & (1 << i))) + continue; + sysfs_remove_group(&pdev->dev.kobj, &pc87427_group_fan[i]); + } + kfree(data); + + return 0; +} + + +static struct platform_driver pc87427_driver = { + .driver = { + .owner = THIS_MODULE, + .name = DRVNAME, + }, + .probe = pc87427_probe, + .remove = __devexit_p(pc87427_remove), +}; + +static int __init pc87427_device_add(unsigned short address) +{ + struct resource res = { + .start = address, + .end = address + REGION_LENGTH - 1, + .name = logdev_str[0], + .flags = IORESOURCE_IO, + }; + int err; + + pdev = platform_device_alloc(DRVNAME, address); + if (!pdev) { + err = -ENOMEM; + printk(KERN_ERR DRVNAME ": Device allocation failed\n"); + goto exit; + } + + err = platform_device_add_resources(pdev, &res, 1); + if (err) { + printk(KERN_ERR DRVNAME ": Device resource addition failed " + "(%d)\n", err); + goto exit_device_put; + } + + err = platform_device_add(pdev); + if (err) { + printk(KERN_ERR DRVNAME ": Device addition failed (%d)\n", + err); + goto exit_device_put; + } + + return 0; + +exit_device_put: + platform_device_put(pdev); +exit: + return err; +} + +static int __init pc87427_find(int sioaddr, unsigned short *address) +{ + u16 val; + int i, err = 0; + + /* Identify device */ + val = superio_inb(sioaddr, SIOREG_DEVID); + if (val != 0xf2) { /* PC87427 */ + err = -ENODEV; + goto exit; + } + + for (i = 0; i < 2; i++) { + address[i] = 0; + /* Select logical device */ + superio_outb(sioaddr, SIOREG_LDSEL, logdev[i]); + + val = superio_inb(sioaddr, SIOREG_ACT); + if (!(val & 0x01)) { + printk(KERN_INFO DRVNAME ": Logical device 0x%02x " + "not activated\n", logdev[i]); + continue; + } + + val = superio_inb(sioaddr, SIOREG_MAP); + if (val & 0x01) { + printk(KERN_WARNING DRVNAME ": Logical device 0x%02x " + "is memory-mapped, can't use\n", logdev[i]); + continue; + } + + val = (superio_inb(sioaddr, SIOREG_IOBASE) << 8) + | superio_inb(sioaddr, SIOREG_IOBASE + 1); + if (!val) { + printk(KERN_INFO DRVNAME ": I/O base address not set " + "for logical device 0x%02x\n", logdev[i]); + continue; + } + address[i] = val; + } + +exit: + superio_exit(sioaddr); + return err; +} + +static int __init pc87427_init(void) +{ + int err; + unsigned short address[2]; + + if (pc87427_find(0x2e, address) + && pc87427_find(0x4e, address)) + return -ENODEV; + + /* For now the driver only handles fans so we only care about the + first address. */ + if (!address[0]) + return -ENODEV; + + err = platform_driver_register(&pc87427_driver); + if (err) + goto exit; + + /* Sets global pdev as a side effect */ + err = pc87427_device_add(address[0]); + if (err) + goto exit_driver; + + return 0; + +exit_driver: + platform_driver_unregister(&pc87427_driver); +exit: + return err; +} + +static void __exit pc87427_exit(void) +{ + platform_device_unregister(pdev); + platform_driver_unregister(&pc87427_driver); +} + +MODULE_AUTHOR("Jean Delvare <khali@linux-fr.org>"); +MODULE_DESCRIPTION("PC87427 hardware monitoring driver"); +MODULE_LICENSE("GPL"); + +module_init(pc87427_init); +module_exit(pc87427_exit); diff --git a/drivers/hwmon/w83627ehf.c b/drivers/hwmon/w83627ehf.c index 2257806d0102..212a1558c63b 100644 --- a/drivers/hwmon/w83627ehf.c +++ b/drivers/hwmon/w83627ehf.c @@ -3,7 +3,7 @@ the Winbond W83627EHF Super-I/O chip Copyright (C) 2005 Jean Delvare <khali@linux-fr.org> Copyright (C) 2006 Yuan Mu (Winbond), - Rudolf Marek <r.marek@sh.cvut.cz> + Rudolf Marek <r.marek@assembler.cz> David Hubbard <david.c.hubbard@gmail.com> Shamelessly ripped from the w83627hf driver diff --git a/drivers/hwmon/w83792d.c b/drivers/hwmon/w83792d.c index 4e108262576f..b0fa296740d1 100644 --- a/drivers/hwmon/w83792d.c +++ b/drivers/hwmon/w83792d.c @@ -3,7 +3,7 @@ monitoring Copyright (C) 2004, 2005 Winbond Electronics Corp. Chunhao Huang <DZShen@Winbond.com.tw>, - Rudolf Marek <r.marek@sh.cvut.cz> + Rudolf Marek <r.marek@assembler.cz> 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 diff --git a/drivers/hwmon/w83793.c b/drivers/hwmon/w83793.c new file mode 100644 index 000000000000..c12ac5abc2bb --- /dev/null +++ b/drivers/hwmon/w83793.c @@ -0,0 +1,1609 @@ +/* + w83793.c - Linux kernel driver for hardware monitoring + Copyright (C) 2006 Winbond Electronics Corp. + Yuan Mu + Rudolf Marek <r.marek@assembler.cz> + + 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 - version 2. + + 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., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +/* + Supports following chips: + + Chip #vin #fanin #pwm #temp wchipid vendid i2c ISA + w83793 10 12 8 6 0x7b 0x5ca3 yes no +*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/hwmon.h> +#include <linux/hwmon-vid.h> +#include <linux/hwmon-sysfs.h> +#include <linux/err.h> +#include <linux/mutex.h> + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { 0x2c, 0x2d, 0x2e, 0x2f, I2C_CLIENT_END }; + +/* Insmod parameters */ +I2C_CLIENT_INSMOD_1(w83793); +I2C_CLIENT_MODULE_PARM(force_subclients, "List of subclient addresses: " + "{bus, clientaddr, subclientaddr1, subclientaddr2}"); + +static int reset; +module_param(reset, bool, 0); +MODULE_PARM_DESC(reset, "Set to 1 to reset chip, not recommended"); + +/* + Address 0x00, 0x0d, 0x0e, 0x0f in all three banks are reserved + as ID, Bank Select registers +*/ +#define W83793_REG_BANKSEL 0x00 +#define W83793_REG_VENDORID 0x0d +#define W83793_REG_CHIPID 0x0e +#define W83793_REG_DEVICEID 0x0f + +#define W83793_REG_CONFIG 0x40 +#define W83793_REG_MFC 0x58 +#define W83793_REG_FANIN_CTRL 0x5c +#define W83793_REG_FANIN_SEL 0x5d +#define W83793_REG_I2C_ADDR 0x0b +#define W83793_REG_I2C_SUBADDR 0x0c +#define W83793_REG_VID_INA 0x05 +#define W83793_REG_VID_INB 0x06 +#define W83793_REG_VID_LATCHA 0x07 +#define W83793_REG_VID_LATCHB 0x08 +#define W83793_REG_VID_CTRL 0x59 + +static u16 W83793_REG_TEMP_MODE[2] = { 0x5e, 0x5f }; + +#define TEMP_READ 0 +#define TEMP_CRIT 1 +#define TEMP_CRIT_HYST 2 +#define TEMP_WARN 3 +#define TEMP_WARN_HYST 4 +/* only crit and crit_hyst affect real-time alarm status + current crit crit_hyst warn warn_hyst */ +static u16 W83793_REG_TEMP[][5] = { + {0x1c, 0x78, 0x79, 0x7a, 0x7b}, + {0x1d, 0x7c, 0x7d, 0x7e, 0x7f}, + {0x1e, 0x80, 0x81, 0x82, 0x83}, + {0x1f, 0x84, 0x85, 0x86, 0x87}, + {0x20, 0x88, 0x89, 0x8a, 0x8b}, + {0x21, 0x8c, 0x8d, 0x8e, 0x8f}, +}; + +#define W83793_REG_TEMP_LOW_BITS 0x22 + +#define W83793_REG_BEEP(index) (0x53 + (index)) +#define W83793_REG_ALARM(index) (0x4b + (index)) + +#define W83793_REG_CLR_CHASSIS 0x4a /* SMI MASK4 */ +#define W83793_REG_IRQ_CTRL 0x50 +#define W83793_REG_OVT_CTRL 0x51 +#define W83793_REG_OVT_BEEP 0x52 + +#define IN_READ 0 +#define IN_MAX 1 +#define IN_LOW 2 +static const u16 W83793_REG_IN[][3] = { + /* Current, High, Low */ + {0x10, 0x60, 0x61}, /* Vcore A */ + {0x11, 0x62, 0x63}, /* Vcore B */ + {0x12, 0x64, 0x65}, /* Vtt */ + {0x14, 0x6a, 0x6b}, /* VSEN1 */ + {0x15, 0x6c, 0x6d}, /* VSEN2 */ + {0x16, 0x6e, 0x6f}, /* +3VSEN */ + {0x17, 0x70, 0x71}, /* +12VSEN */ + {0x18, 0x72, 0x73}, /* 5VDD */ + {0x19, 0x74, 0x75}, /* 5VSB */ + {0x1a, 0x76, 0x77}, /* VBAT */ +}; + +/* Low Bits of Vcore A/B Vtt Read/High/Low */ +static const u16 W83793_REG_IN_LOW_BITS[] = { 0x1b, 0x68, 0x69 }; +static u8 scale_in[] = { 2, 2, 2, 16, 16, 16, 8, 24, 24, 16 }; + +#define W83793_REG_FAN(index) (0x23 + 2 * (index)) /* High byte */ +#define W83793_REG_FAN_MIN(index) (0x90 + 2 * (index)) /* High byte */ + +#define W83793_REG_PWM_DEFAULT 0xb2 +#define W83793_REG_PWM_ENABLE 0x207 +#define W83793_REG_PWM_UPTIME 0xc3 /* Unit in 0.1 second */ +#define W83793_REG_PWM_DOWNTIME 0xc4 /* Unit in 0.1 second */ +#define W83793_REG_TEMP_CRITICAL 0xc5 + +#define PWM_DUTY 0 +#define PWM_START 1 +#define PWM_NONSTOP 2 +#define W83793_REG_PWM(index, nr) (((nr) == 0 ? 0xb3 : \ + (nr) == 1 ? 0x220 : 0x218) + (index)) + +/* bit field, fan1 is bit0, fan2 is bit1 ... */ +#define W83793_REG_TEMP_FAN_MAP(index) (0x201 + (index)) +#define W83793_REG_TEMP_TOL(index) (0x208 + (index)) +#define W83793_REG_TEMP_CRUISE(index) (0x210 + (index)) +#define W83793_REG_PWM_STOP_TIME(index) (0x228 + (index)) +#define W83793_REG_SF2_TEMP(index, nr) (0x230 + ((index) << 4) + (nr)) +#define W83793_REG_SF2_PWM(index, nr) (0x238 + ((index) << 4) + (nr)) + +static inline unsigned long FAN_FROM_REG(u16 val) +{ + if ((val >= 0xfff) || (val == 0)) + return 0; + return (1350000UL / val); +} + +static inline u16 FAN_TO_REG(long rpm) +{ + if (rpm <= 0) + return 0x0fff; + return SENSORS_LIMIT((1350000 + (rpm >> 1)) / rpm, 1, 0xffe); +} + +static inline unsigned long TIME_FROM_REG(u8 reg) +{ + return (reg * 100); +} + +static inline u8 TIME_TO_REG(unsigned long val) +{ + return SENSORS_LIMIT((val + 50) / 100, 0, 0xff); +} + +static inline long TEMP_FROM_REG(s8 reg) +{ + return (reg * 1000); +} + +static inline s8 TEMP_TO_REG(long val, s8 min, s8 max) +{ + return SENSORS_LIMIT((val + (val < 0 ? -500 : 500)) / 1000, min, max); +} + +struct w83793_data { + struct i2c_client client; + struct i2c_client *lm75[2]; + struct class_device *class_dev; + struct mutex update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + unsigned long last_nonvolatile; /* In jiffies, last time we update the + nonvolatile registers */ + + u8 bank; + u8 vrm; + u8 vid[2]; + u8 in[10][3]; /* Register value, read/high/low */ + u8 in_low_bits[3]; /* Additional resolution for VCore A/B Vtt */ + + u16 has_fan; /* Only fan1- fan5 has own pins */ + u16 fan[12]; /* Register value combine */ + u16 fan_min[12]; /* Register value combine */ + + s8 temp[6][5]; /* current, crit, crit_hyst,warn, warn_hyst */ + u8 temp_low_bits; /* Additional resolution TD1-TD4 */ + u8 temp_mode[2]; /* byte 0: Temp D1-D4 mode each has 2 bits + byte 1: Temp R1,R2 mode, each has 1 bit */ + u8 temp_critical; /* If reached all fan will be at full speed */ + u8 temp_fan_map[6]; /* Temp controls which pwm fan, bit field */ + + u8 has_pwm; + u8 pwm_enable; /* Register value, each Temp has 1 bit */ + u8 pwm_uptime; /* Register value */ + u8 pwm_downtime; /* Register value */ + u8 pwm_default; /* All fan default pwm, next poweron valid */ + u8 pwm[8][3]; /* Register value */ + u8 pwm_stop_time[8]; + u8 temp_cruise[6]; + + u8 alarms[5]; /* realtime status registers */ + u8 beeps[5]; + u8 beep_enable; + u8 tolerance[3]; /* Temp tolerance(Smart Fan I/II) */ + u8 sf2_pwm[6][7]; /* Smart FanII: Fan duty cycle */ + u8 sf2_temp[6][7]; /* Smart FanII: Temp level point */ +}; + +static u8 w83793_read_value(struct i2c_client *client, u16 reg); +static int w83793_write_value(struct i2c_client *client, u16 reg, u8 value); +static int w83793_attach_adapter(struct i2c_adapter *adapter); +static int w83793_detect(struct i2c_adapter *adapter, int address, int kind); +static int w83793_detach_client(struct i2c_client *client); +static void w83793_init_client(struct i2c_client *client); +static void w83793_update_nonvolatile(struct device *dev); +static struct w83793_data *w83793_update_device(struct device *dev); + +static struct i2c_driver w83793_driver = { + .driver = { + .name = "w83793", + }, + .attach_adapter = w83793_attach_adapter, + .detach_client = w83793_detach_client, +}; + +static ssize_t +show_vrm(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83793_data *data = i2c_get_clientdata(client); + + return sprintf(buf, "%d\n", data->vrm); +} + +static ssize_t +show_vid(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83793_data *data = w83793_update_device(dev); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int index = sensor_attr->index; + + return sprintf(buf, "%d\n", vid_from_reg(data->vid[index], data->vrm)); +} + +static ssize_t +store_vrm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83793_data *data = i2c_get_clientdata(client); + + data->vrm = simple_strtoul(buf, NULL, 10); + return count; +} + +#define ALARM_STATUS 0 +#define BEEP_ENABLE 1 +static ssize_t +show_alarm_beep(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83793_data *data = w83793_update_device(dev); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index >> 3; + int bit = sensor_attr->index & 0x07; + u8 val; + + if (ALARM_STATUS == nr) { + val = (data->alarms[index] >> (bit)) & 1; + } else { /* BEEP_ENABLE */ + val = (data->beeps[index] >> (bit)) & 1; + } + + return sprintf(buf, "%u\n", val); +} + +static ssize_t +store_beep(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83793_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int index = sensor_attr->index >> 3; + int shift = sensor_attr->index & 0x07; + u8 beep_bit = 1 << shift; + u8 val; + + val = simple_strtoul(buf, NULL, 10); + if (val != 0 && val != 1) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->beeps[index] = w83793_read_value(client, W83793_REG_BEEP(index)); + data->beeps[index] &= ~beep_bit; + data->beeps[index] |= val << shift; + w83793_write_value(client, W83793_REG_BEEP(index), data->beeps[index]); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t +show_beep_enable(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83793_data *data = w83793_update_device(dev); + return sprintf(buf, "%u\n", (data->beep_enable >> 1) & 0x01); +} + +static ssize_t +store_beep_enable(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83793_data *data = i2c_get_clientdata(client); + u8 val = simple_strtoul(buf, NULL, 10); + + if (val != 0 && val != 1) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->beep_enable = w83793_read_value(client, W83793_REG_OVT_BEEP) + & 0xfd; + data->beep_enable |= val << 1; + w83793_write_value(client, W83793_REG_OVT_BEEP, data->beep_enable); + mutex_unlock(&data->update_lock); + + return count; +} + +/* Write any value to clear chassis alarm */ +static ssize_t +store_chassis_clear(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83793_data *data = i2c_get_clientdata(client); + u8 val; + + mutex_lock(&data->update_lock); + val = w83793_read_value(client, W83793_REG_CLR_CHASSIS); + val |= 0x80; + w83793_write_value(client, W83793_REG_CLR_CHASSIS, val); + mutex_unlock(&data->update_lock); + return count; +} + +#define FAN_INPUT 0 +#define FAN_MIN 1 +static ssize_t +show_fan(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + struct w83793_data *data = w83793_update_device(dev); + u16 val; + + if (FAN_INPUT == nr) { + val = data->fan[index] & 0x0fff; + } else { + val = data->fan_min[index] & 0x0fff; + } + + return sprintf(buf, "%lu\n", FAN_FROM_REG(val)); +} + +static ssize_t +store_fan_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int index = sensor_attr->index; + struct i2c_client *client = to_i2c_client(dev); + struct w83793_data *data = i2c_get_clientdata(client); + u16 val = FAN_TO_REG(simple_strtoul(buf, NULL, 10)); + + mutex_lock(&data->update_lock); + data->fan_min[index] = val; + w83793_write_value(client, W83793_REG_FAN_MIN(index), + (val >> 8) & 0xff); + w83793_write_value(client, W83793_REG_FAN_MIN(index) + 1, val & 0xff); + mutex_unlock(&data->update_lock); + + return count; +} + +#define PWM_DUTY 0 +#define PWM_START 1 +#define PWM_NONSTOP 2 +#define PWM_STOP_TIME 3 +static ssize_t +show_pwm(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + struct w83793_data *data = w83793_update_device(dev); + u16 val; + int nr = sensor_attr->nr; + int index = sensor_attr->index; + + if (PWM_STOP_TIME == nr) + val = TIME_FROM_REG(data->pwm_stop_time[index]); + else + val = (data->pwm[index][nr] & 0x3f) << 2; + + return sprintf(buf, "%d\n", val); +} + +static ssize_t +store_pwm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83793_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + u8 val; + + mutex_lock(&data->update_lock); + if (PWM_STOP_TIME == nr) { + val = TIME_TO_REG(simple_strtoul(buf, NULL, 10)); + data->pwm_stop_time[index] = val; + w83793_write_value(client, W83793_REG_PWM_STOP_TIME(index), + val); + } else { + val = SENSORS_LIMIT(simple_strtoul(buf, NULL, 10), 0, 0xff) + >> 2; + data->pwm[index][nr] = + w83793_read_value(client, W83793_REG_PWM(index, nr)) & 0xc0; + data->pwm[index][nr] |= val; + w83793_write_value(client, W83793_REG_PWM(index, nr), + data->pwm[index][nr]); + } + + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +show_temp(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + struct w83793_data *data = w83793_update_device(dev); + long temp = TEMP_FROM_REG(data->temp[index][nr]); + + if (TEMP_READ == nr && index < 4) { /* Only TD1-TD4 have low bits */ + int low = ((data->temp_low_bits >> (index * 2)) & 0x03) * 250; + temp += temp > 0 ? low : -low; + } + return sprintf(buf, "%ld\n", temp); +} + +static ssize_t +store_temp(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + struct i2c_client *client = to_i2c_client(dev); + struct w83793_data *data = i2c_get_clientdata(client); + long tmp = simple_strtol(buf, NULL, 10); + + mutex_lock(&data->update_lock); + data->temp[index][nr] = TEMP_TO_REG(tmp, -128, 127); + w83793_write_value(client, W83793_REG_TEMP[index][nr], + data->temp[index][nr]); + mutex_unlock(&data->update_lock); + return count; +} + +/* + TD1-TD4 + each has 4 mode:(2 bits) + 0: Stop monitor + 1: Use internal temp sensor(default) + 2: Use sensor in AMD CPU and get result by AMDSI + 3: Use sensor in Intel CPU and get result by PECI + + TR1-TR2 + each has 2 mode:(1 bit) + 0: Disable temp sensor monitor + 1: To enable temp sensors monitor +*/ + +/* 0 disable, 5 AMDSI, 6 PECI */ +static u8 TO_TEMP_MODE[] = { 0, 0, 5, 6 }; + +static ssize_t +show_temp_mode(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83793_data *data = w83793_update_device(dev); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int index = sensor_attr->index; + u8 mask = (index < 4) ? 0x03 : 0x01; + u8 shift = (index < 4) ? (2 * index) : (index - 4); + u8 tmp; + index = (index < 4) ? 0 : 1; + + tmp = (data->temp_mode[index] >> shift) & mask; + + /* for the internal sensor, found out if diode or thermistor */ + if (tmp == 1) { + tmp = index == 0 ? 3 : 4; + } else { + tmp = TO_TEMP_MODE[tmp]; + } + + return sprintf(buf, "%d\n", tmp); +} + +static ssize_t +store_temp_mode(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83793_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int index = sensor_attr->index; + u8 mask = (index < 4) ? 0x03 : 0x01; + u8 shift = (index < 4) ? (2 * index) : (index - 4); + u8 val = simple_strtoul(buf, NULL, 10); + + /* transform the sysfs interface values into table above */ + if ((val == 5 || val == 6) && (index < 4)) { + val -= 3; + } else if ((val == 3 && index < 4) + || (val == 4 && index >= 4) + || val == 0) { + /* transform diode or thermistor into internal enable */ + val = !!val; + } else { + return -EINVAL; + } + + index = (index < 4) ? 0 : 1; + mutex_lock(&data->update_lock); + data->temp_mode[index] = + w83793_read_value(client, W83793_REG_TEMP_MODE[index]); + data->temp_mode[index] &= ~(mask << shift); + data->temp_mode[index] |= val << shift; + w83793_write_value(client, W83793_REG_TEMP_MODE[index], + data->temp_mode[index]); + mutex_unlock(&data->update_lock); + + return count; +} + +#define SETUP_PWM_DEFAULT 0 +#define SETUP_PWM_UPTIME 1 /* Unit in 0.1s */ +#define SETUP_PWM_DOWNTIME 2 /* Unit in 0.1s */ +#define SETUP_TEMP_CRITICAL 3 +static ssize_t +show_sf_setup(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + struct w83793_data *data = w83793_update_device(dev); + u32 val = 0; + + if (SETUP_PWM_DEFAULT == nr) { + val = (data->pwm_default & 0x3f) << 2; + } else if (SETUP_PWM_UPTIME == nr) { + val = TIME_FROM_REG(data->pwm_uptime); + } else if (SETUP_PWM_DOWNTIME == nr) { + val = TIME_FROM_REG(data->pwm_downtime); + } else if (SETUP_TEMP_CRITICAL == nr) { + val = TEMP_FROM_REG(data->temp_critical & 0x7f); + } + + return sprintf(buf, "%d\n", val); +} + +static ssize_t +store_sf_setup(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + struct i2c_client *client = to_i2c_client(dev); + struct w83793_data *data = i2c_get_clientdata(client); + + mutex_lock(&data->update_lock); + if (SETUP_PWM_DEFAULT == nr) { + data->pwm_default = + w83793_read_value(client, W83793_REG_PWM_DEFAULT) & 0xc0; + data->pwm_default |= SENSORS_LIMIT(simple_strtoul(buf, NULL, + 10), + 0, 0xff) >> 2; + w83793_write_value(client, W83793_REG_PWM_DEFAULT, + data->pwm_default); + } else if (SETUP_PWM_UPTIME == nr) { + data->pwm_uptime = TIME_TO_REG(simple_strtoul(buf, NULL, 10)); + data->pwm_uptime += data->pwm_uptime == 0 ? 1 : 0; + w83793_write_value(client, W83793_REG_PWM_UPTIME, + data->pwm_uptime); + } else if (SETUP_PWM_DOWNTIME == nr) { + data->pwm_downtime = TIME_TO_REG(simple_strtoul(buf, NULL, 10)); + data->pwm_downtime += data->pwm_downtime == 0 ? 1 : 0; + w83793_write_value(client, W83793_REG_PWM_DOWNTIME, + data->pwm_downtime); + } else { /* SETUP_TEMP_CRITICAL */ + data->temp_critical = + w83793_read_value(client, W83793_REG_TEMP_CRITICAL) & 0x80; + data->temp_critical |= TEMP_TO_REG(simple_strtol(buf, NULL, 10), + 0, 0x7f); + w83793_write_value(client, W83793_REG_TEMP_CRITICAL, + data->temp_critical); + } + + mutex_unlock(&data->update_lock); + return count; +} + +/* + Temp SmartFan control + TEMP_FAN_MAP + Temp channel control which pwm fan, bitfield, bit 0 indicate pwm1... + It's possible two or more temp channels control the same fan, w83793 + always prefers to pick the most critical request and applies it to + the related Fan. + It's possible one fan is not in any mapping of 6 temp channels, this + means the fan is manual mode + + TEMP_PWM_ENABLE + Each temp channel has its own SmartFan mode, and temp channel + control fans that are set by TEMP_FAN_MAP + 0: SmartFanII mode + 1: Thermal Cruise Mode + + TEMP_CRUISE + Target temperature in thermal cruise mode, w83793 will try to turn + fan speed to keep the temperature of target device around this + temperature. + + TEMP_TOLERANCE + If Temp higher or lower than target with this tolerance, w83793 + will take actions to speed up or slow down the fan to keep the + temperature within the tolerance range. +*/ + +#define TEMP_FAN_MAP 0 +#define TEMP_PWM_ENABLE 1 +#define TEMP_CRUISE 2 +#define TEMP_TOLERANCE 3 +static ssize_t +show_sf_ctrl(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + struct w83793_data *data = w83793_update_device(dev); + u32 val; + + if (TEMP_FAN_MAP == nr) { + val = data->temp_fan_map[index]; + } else if (TEMP_PWM_ENABLE == nr) { + /* +2 to transfrom into 2 and 3 to conform with sysfs intf */ + val = ((data->pwm_enable >> index) & 0x01) + 2; + } else if (TEMP_CRUISE == nr) { + val = TEMP_FROM_REG(data->temp_cruise[index] & 0x7f); + } else { /* TEMP_TOLERANCE */ + val = data->tolerance[index >> 1] >> ((index & 0x01) ? 4 : 0); + val = TEMP_FROM_REG(val & 0x0f); + } + return sprintf(buf, "%d\n", val); +} + +static ssize_t +store_sf_ctrl(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + struct i2c_client *client = to_i2c_client(dev); + struct w83793_data *data = i2c_get_clientdata(client); + u32 val; + + mutex_lock(&data->update_lock); + if (TEMP_FAN_MAP == nr) { + val = simple_strtoul(buf, NULL, 10) & 0xff; + w83793_write_value(client, W83793_REG_TEMP_FAN_MAP(index), val); + data->temp_fan_map[index] = val; + } else if (TEMP_PWM_ENABLE == nr) { + val = simple_strtoul(buf, NULL, 10); + if (2 == val || 3 == val) { + data->pwm_enable = + w83793_read_value(client, W83793_REG_PWM_ENABLE); + if (val - 2) + data->pwm_enable |= 1 << index; + else + data->pwm_enable &= ~(1 << index); + w83793_write_value(client, W83793_REG_PWM_ENABLE, + data->pwm_enable); + } else { + mutex_unlock(&data->update_lock); + return -EINVAL; + } + } else if (TEMP_CRUISE == nr) { + data->temp_cruise[index] = + w83793_read_value(client, W83793_REG_TEMP_CRUISE(index)); + val = TEMP_TO_REG(simple_strtol(buf, NULL, 10), 0, 0x7f); + data->temp_cruise[index] &= 0x80; + data->temp_cruise[index] |= val; + + w83793_write_value(client, W83793_REG_TEMP_CRUISE(index), + data->temp_cruise[index]); + } else { /* TEMP_TOLERANCE */ + int i = index >> 1; + u8 shift = (index & 0x01) ? 4 : 0; + data->tolerance[i] = + w83793_read_value(client, W83793_REG_TEMP_TOL(i)); + + val = TEMP_TO_REG(simple_strtol(buf, NULL, 10), 0, 0x0f); + data->tolerance[i] &= ~(0x0f << shift); + data->tolerance[i] |= val << shift; + w83793_write_value(client, W83793_REG_TEMP_TOL(i), + data->tolerance[i]); + } + + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +show_sf2_pwm(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + struct w83793_data *data = w83793_update_device(dev); + + return sprintf(buf, "%d\n", (data->sf2_pwm[index][nr] & 0x3f) << 2); +} + +static ssize_t +store_sf2_pwm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83793_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + u8 val = SENSORS_LIMIT(simple_strtoul(buf, NULL, 10), 0, 0xff) >> 2; + + mutex_lock(&data->update_lock); + data->sf2_pwm[index][nr] = + w83793_read_value(client, W83793_REG_SF2_PWM(index, nr)) & 0xc0; + data->sf2_pwm[index][nr] |= val; + w83793_write_value(client, W83793_REG_SF2_PWM(index, nr), + data->sf2_pwm[index][nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +show_sf2_temp(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + struct w83793_data *data = w83793_update_device(dev); + + return sprintf(buf, "%ld\n", + TEMP_FROM_REG(data->sf2_temp[index][nr] & 0x7f)); +} + +static ssize_t +store_sf2_temp(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83793_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + u8 val = TEMP_TO_REG(simple_strtol(buf, NULL, 10), 0, 0x7f); + + mutex_lock(&data->update_lock); + data->sf2_temp[index][nr] = + w83793_read_value(client, W83793_REG_SF2_TEMP(index, nr)) & 0x80; + data->sf2_temp[index][nr] |= val; + w83793_write_value(client, W83793_REG_SF2_TEMP(index, nr), + data->sf2_temp[index][nr]); + mutex_unlock(&data->update_lock); + return count; +} + +/* only Vcore A/B and Vtt have additional 2 bits precision */ +static ssize_t +show_in(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + struct w83793_data *data = w83793_update_device(dev); + u16 val = data->in[index][nr]; + + if (index < 3) { + val <<= 2; + val += (data->in_low_bits[nr] >> (index * 2)) & 0x3; + } + return sprintf(buf, "%d\n", val * scale_in[index]); +} + +static ssize_t +store_in(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + struct i2c_client *client = to_i2c_client(dev); + struct w83793_data *data = i2c_get_clientdata(client); + u32 val; + + val = + (simple_strtoul(buf, NULL, 10) + + scale_in[index] / 2) / scale_in[index]; + mutex_lock(&data->update_lock); + if (index > 2) { + val = SENSORS_LIMIT(val, 0, 255); + } else { + val = SENSORS_LIMIT(val, 0, 0x3FF); + data->in_low_bits[nr] = + w83793_read_value(client, W83793_REG_IN_LOW_BITS[nr]); + data->in_low_bits[nr] &= ~(0x03 << (2 * index)); + data->in_low_bits[nr] |= (val & 0x03) << (2 * index); + w83793_write_value(client, W83793_REG_IN_LOW_BITS[nr], + data->in_low_bits[nr]); + val >>= 2; + } + data->in[index][nr] = val; + w83793_write_value(client, W83793_REG_IN[index][nr], + data->in[index][nr]); + mutex_unlock(&data->update_lock); + return count; +} + +#define NOT_USED -1 + +#define SENSOR_ATTR_IN(index) \ + SENSOR_ATTR_2(in##index##_input, S_IRUGO, show_in, NULL, \ + IN_READ, index), \ + SENSOR_ATTR_2(in##index##_max, S_IRUGO | S_IWUSR, show_in, \ + store_in, IN_MAX, index), \ + SENSOR_ATTR_2(in##index##_min, S_IRUGO | S_IWUSR, show_in, \ + store_in, IN_LOW, index), \ + SENSOR_ATTR_2(in##index##_alarm, S_IRUGO, show_alarm_beep, \ + NULL, ALARM_STATUS, index + ((index > 2) ? 1 : 0)), \ + SENSOR_ATTR_2(in##index##_beep, S_IWUSR | S_IRUGO, \ + show_alarm_beep, store_beep, BEEP_ENABLE, \ + index + ((index > 2) ? 1 : 0)) + +#define SENSOR_ATTR_FAN(index) \ + SENSOR_ATTR_2(fan##index##_alarm, S_IRUGO, show_alarm_beep, \ + NULL, ALARM_STATUS, index + 17), \ + SENSOR_ATTR_2(fan##index##_beep, S_IWUSR | S_IRUGO, \ + show_alarm_beep, store_beep, BEEP_ENABLE, index + 17), \ + SENSOR_ATTR_2(fan##index##_input, S_IRUGO, show_fan, \ + NULL, FAN_INPUT, index - 1), \ + SENSOR_ATTR_2(fan##index##_min, S_IWUSR | S_IRUGO, \ + show_fan, store_fan_min, FAN_MIN, index - 1) + +#define SENSOR_ATTR_PWM(index) \ + SENSOR_ATTR_2(pwm##index, S_IWUSR | S_IRUGO, show_pwm, \ + store_pwm, PWM_DUTY, index - 1), \ + SENSOR_ATTR_2(pwm##index##_nonstop, S_IWUSR | S_IRUGO, \ + show_pwm, store_pwm, PWM_NONSTOP, index - 1), \ + SENSOR_ATTR_2(pwm##index##_start, S_IWUSR | S_IRUGO, \ + show_pwm, store_pwm, PWM_START, index - 1), \ + SENSOR_ATTR_2(pwm##index##_stop_time, S_IWUSR | S_IRUGO, \ + show_pwm, store_pwm, PWM_STOP_TIME, index - 1) + +#define SENSOR_ATTR_TEMP(index) \ + SENSOR_ATTR_2(temp##index##_type, S_IRUGO | S_IWUSR, \ + show_temp_mode, store_temp_mode, NOT_USED, index - 1), \ + SENSOR_ATTR_2(temp##index##_input, S_IRUGO, show_temp, \ + NULL, TEMP_READ, index - 1), \ + SENSOR_ATTR_2(temp##index##_max, S_IRUGO | S_IWUSR, show_temp, \ + store_temp, TEMP_CRIT, index - 1), \ + SENSOR_ATTR_2(temp##index##_max_hyst, S_IRUGO | S_IWUSR, \ + show_temp, store_temp, TEMP_CRIT_HYST, index - 1), \ + SENSOR_ATTR_2(temp##index##_warn, S_IRUGO | S_IWUSR, show_temp, \ + store_temp, TEMP_WARN, index - 1), \ + SENSOR_ATTR_2(temp##index##_warn_hyst, S_IRUGO | S_IWUSR, \ + show_temp, store_temp, TEMP_WARN_HYST, index - 1), \ + SENSOR_ATTR_2(temp##index##_alarm, S_IRUGO, \ + show_alarm_beep, NULL, ALARM_STATUS, index + 11), \ + SENSOR_ATTR_2(temp##index##_beep, S_IWUSR | S_IRUGO, \ + show_alarm_beep, store_beep, BEEP_ENABLE, index + 11), \ + SENSOR_ATTR_2(temp##index##_auto_channels_pwm, \ + S_IRUGO | S_IWUSR, show_sf_ctrl, store_sf_ctrl, \ + TEMP_FAN_MAP, index - 1), \ + SENSOR_ATTR_2(temp##index##_pwm_enable, S_IWUSR | S_IRUGO, \ + show_sf_ctrl, store_sf_ctrl, TEMP_PWM_ENABLE, \ + index - 1), \ + SENSOR_ATTR_2(thermal_cruise##index, S_IRUGO | S_IWUSR, \ + show_sf_ctrl, store_sf_ctrl, TEMP_CRUISE, index - 1), \ + SENSOR_ATTR_2(tolerance##index, S_IRUGO | S_IWUSR, show_sf_ctrl,\ + store_sf_ctrl, TEMP_TOLERANCE, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point1_pwm, S_IRUGO | S_IWUSR, \ + show_sf2_pwm, store_sf2_pwm, 0, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point2_pwm, S_IRUGO | S_IWUSR, \ + show_sf2_pwm, store_sf2_pwm, 1, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point3_pwm, S_IRUGO | S_IWUSR, \ + show_sf2_pwm, store_sf2_pwm, 2, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point4_pwm, S_IRUGO | S_IWUSR, \ + show_sf2_pwm, store_sf2_pwm, 3, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point5_pwm, S_IRUGO | S_IWUSR, \ + show_sf2_pwm, store_sf2_pwm, 4, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point6_pwm, S_IRUGO | S_IWUSR, \ + show_sf2_pwm, store_sf2_pwm, 5, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point7_pwm, S_IRUGO | S_IWUSR, \ + show_sf2_pwm, store_sf2_pwm, 6, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point1_temp, S_IRUGO | S_IWUSR,\ + show_sf2_temp, store_sf2_temp, 0, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point2_temp, S_IRUGO | S_IWUSR,\ + show_sf2_temp, store_sf2_temp, 1, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point3_temp, S_IRUGO | S_IWUSR,\ + show_sf2_temp, store_sf2_temp, 2, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point4_temp, S_IRUGO | S_IWUSR,\ + show_sf2_temp, store_sf2_temp, 3, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point5_temp, S_IRUGO | S_IWUSR,\ + show_sf2_temp, store_sf2_temp, 4, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point6_temp, S_IRUGO | S_IWUSR,\ + show_sf2_temp, store_sf2_temp, 5, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point7_temp, S_IRUGO | S_IWUSR,\ + show_sf2_temp, store_sf2_temp, 6, index - 1) + +static struct sensor_device_attribute_2 w83793_sensor_attr_2[] = { + SENSOR_ATTR_IN(0), + SENSOR_ATTR_IN(1), + SENSOR_ATTR_IN(2), + SENSOR_ATTR_IN(3), + SENSOR_ATTR_IN(4), + SENSOR_ATTR_IN(5), + SENSOR_ATTR_IN(6), + SENSOR_ATTR_IN(7), + SENSOR_ATTR_IN(8), + SENSOR_ATTR_IN(9), + SENSOR_ATTR_TEMP(1), + SENSOR_ATTR_TEMP(2), + SENSOR_ATTR_TEMP(3), + SENSOR_ATTR_TEMP(4), + SENSOR_ATTR_TEMP(5), + SENSOR_ATTR_TEMP(6), + SENSOR_ATTR_FAN(1), + SENSOR_ATTR_FAN(2), + SENSOR_ATTR_FAN(3), + SENSOR_ATTR_FAN(4), + SENSOR_ATTR_FAN(5), + SENSOR_ATTR_PWM(1), + SENSOR_ATTR_PWM(2), + SENSOR_ATTR_PWM(3), +}; + +/* Fan6-Fan12 */ +static struct sensor_device_attribute_2 w83793_left_fan[] = { + SENSOR_ATTR_FAN(6), + SENSOR_ATTR_FAN(7), + SENSOR_ATTR_FAN(8), + SENSOR_ATTR_FAN(9), + SENSOR_ATTR_FAN(10), + SENSOR_ATTR_FAN(11), + SENSOR_ATTR_FAN(12), +}; + +/* Pwm4-Pwm8 */ +static struct sensor_device_attribute_2 w83793_left_pwm[] = { + SENSOR_ATTR_PWM(4), + SENSOR_ATTR_PWM(5), + SENSOR_ATTR_PWM(6), + SENSOR_ATTR_PWM(7), + SENSOR_ATTR_PWM(8), +}; + +static struct sensor_device_attribute_2 sda_single_files[] = { + SENSOR_ATTR_2(cpu0_vid, S_IRUGO, show_vid, NULL, NOT_USED, 0), + SENSOR_ATTR_2(cpu1_vid, S_IRUGO, show_vid, NULL, NOT_USED, 1), + SENSOR_ATTR_2(vrm, S_IWUSR | S_IRUGO, show_vrm, store_vrm, + NOT_USED, NOT_USED), + SENSOR_ATTR_2(chassis, S_IWUSR | S_IRUGO, show_alarm_beep, + store_chassis_clear, ALARM_STATUS, 30), + SENSOR_ATTR_2(beep_enable, S_IWUSR | S_IRUGO, show_beep_enable, + store_beep_enable, NOT_USED, NOT_USED), + SENSOR_ATTR_2(pwm_default, S_IWUSR | S_IRUGO, show_sf_setup, + store_sf_setup, SETUP_PWM_DEFAULT, NOT_USED), + SENSOR_ATTR_2(pwm_uptime, S_IWUSR | S_IRUGO, show_sf_setup, + store_sf_setup, SETUP_PWM_UPTIME, NOT_USED), + SENSOR_ATTR_2(pwm_downtime, S_IWUSR | S_IRUGO, show_sf_setup, + store_sf_setup, SETUP_PWM_DOWNTIME, NOT_USED), + SENSOR_ATTR_2(temp_critical, S_IWUSR | S_IRUGO, show_sf_setup, + store_sf_setup, SETUP_TEMP_CRITICAL, NOT_USED), +}; + +static void w83793_init_client(struct i2c_client *client) +{ + if (reset) { + w83793_write_value(client, W83793_REG_CONFIG, 0x80); + } + + /* Start monitoring */ + w83793_write_value(client, W83793_REG_CONFIG, + w83793_read_value(client, W83793_REG_CONFIG) | 0x01); + +} + +static int w83793_attach_adapter(struct i2c_adapter *adapter) +{ + if (!(adapter->class & I2C_CLASS_HWMON)) + return 0; + return i2c_probe(adapter, &addr_data, w83793_detect); +} + +static int w83793_detach_client(struct i2c_client *client) +{ + struct w83793_data *data = i2c_get_clientdata(client); + struct device *dev = &client->dev; + int err, i; + + /* main client */ + if (data) { + hwmon_device_unregister(data->class_dev); + + for (i = 0; i < ARRAY_SIZE(w83793_sensor_attr_2); i++) + device_remove_file(dev, + &w83793_sensor_attr_2[i].dev_attr); + + for (i = 0; i < ARRAY_SIZE(sda_single_files); i++) + device_remove_file(dev, &sda_single_files[i].dev_attr); + + for (i = 0; i < ARRAY_SIZE(w83793_left_fan); i++) + device_remove_file(dev, &w83793_left_fan[i].dev_attr); + + for (i = 0; i < ARRAY_SIZE(w83793_left_pwm); i++) + device_remove_file(dev, &w83793_left_pwm[i].dev_attr); + } + + if ((err = i2c_detach_client(client))) + return err; + + /* main client */ + if (data) + kfree(data); + /* subclient */ + else + kfree(client); + + return 0; +} + +static int +w83793_create_subclient(struct i2c_adapter *adapter, + struct i2c_client *client, int addr, + struct i2c_client **sub_cli) +{ + int err = 0; + struct i2c_client *sub_client; + + (*sub_cli) = sub_client = + kzalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (!(sub_client)) { + return -ENOMEM; + } + sub_client->addr = 0x48 + addr; + i2c_set_clientdata(sub_client, NULL); + sub_client->adapter = adapter; + sub_client->driver = &w83793_driver; + strlcpy(sub_client->name, "w83793 subclient", I2C_NAME_SIZE); + if ((err = i2c_attach_client(sub_client))) { + dev_err(&client->dev, "subclient registration " + "at address 0x%x failed\n", sub_client->addr); + kfree(sub_client); + } + return err; +} + +static int +w83793_detect_subclients(struct i2c_adapter *adapter, int address, + int kind, struct i2c_client *client) +{ + int i, id, err; + u8 tmp; + struct w83793_data *data = i2c_get_clientdata(client); + + id = i2c_adapter_id(adapter); + if (force_subclients[0] == id && force_subclients[1] == address) { + for (i = 2; i <= 3; i++) { + if (force_subclients[i] < 0x48 + || force_subclients[i] > 0x4f) { + dev_err(&client->dev, + "invalid subclient " + "address %d; must be 0x48-0x4f\n", + force_subclients[i]); + err = -EINVAL; + goto ERROR_SC_0; + } + } + w83793_write_value(client, W83793_REG_I2C_SUBADDR, + (force_subclients[2] & 0x07) | + ((force_subclients[3] & 0x07) << 4)); + } + + tmp = w83793_read_value(client, W83793_REG_I2C_SUBADDR); + if (!(tmp & 0x08)) { + err = + w83793_create_subclient(adapter, client, tmp & 0x7, + &data->lm75[0]); + if (err < 0) + goto ERROR_SC_0; + } + if (!(tmp & 0x80)) { + if ((data->lm75[0] != NULL) + && ((tmp & 0x7) == ((tmp >> 4) & 0x7))) { + dev_err(&client->dev, + "duplicate addresses 0x%x, " + "use force_subclients\n", data->lm75[0]->addr); + err = -ENODEV; + goto ERROR_SC_1; + } + err = w83793_create_subclient(adapter, client, + (tmp >> 4) & 0x7, &data->lm75[1]); + if (err < 0) + goto ERROR_SC_1; + } + + return 0; + + /* Undo inits in case of errors */ + +ERROR_SC_1: + if (data->lm75[0] != NULL) { + i2c_detach_client(data->lm75[0]); + kfree(data->lm75[0]); + } +ERROR_SC_0: + return err; +} + +static int w83793_detect(struct i2c_adapter *adapter, int address, int kind) +{ + int i; + u8 tmp, val; + struct i2c_client *client; + struct device *dev; + struct w83793_data *data; + int files_fan = ARRAY_SIZE(w83793_left_fan) / 7; + int files_pwm = ARRAY_SIZE(w83793_left_pwm) / 5; + int err = 0; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + goto exit; + } + + /* OK. For now, we presume we have a valid client. We now create the + client structure, even though we cannot fill it completely yet. + But it allows us to access w83793_{read,write}_value. */ + + if (!(data = kzalloc(sizeof(struct w83793_data), GFP_KERNEL))) { + err = -ENOMEM; + goto exit; + } + + client = &data->client; + dev = &client->dev; + i2c_set_clientdata(client, data); + client->addr = address; + client->adapter = adapter; + client->driver = &w83793_driver; + + data->bank = i2c_smbus_read_byte_data(client, W83793_REG_BANKSEL); + + /* Now, we do the remaining detection. */ + if (kind < 0) { + tmp = data->bank & 0x80 ? 0x5c : 0xa3; + /* Check Winbond vendor ID */ + if (tmp != i2c_smbus_read_byte_data(client, + W83793_REG_VENDORID)) { + pr_debug("w83793: Detection failed at check " + "vendor id\n"); + err = -ENODEV; + goto free_mem; + } + + /* If Winbond chip, address of chip and W83793_REG_I2C_ADDR + should match */ + if ((data->bank & 0x07) == 0 + && i2c_smbus_read_byte_data(client, W83793_REG_I2C_ADDR) != + (address << 1)) { + pr_debug("w83793: Detection failed at check " + "i2c addr\n"); + err = -ENODEV; + goto free_mem; + } + + } + + /* We have either had a force parameter, or we have already detected the + Winbond. Determine the chip type now */ + + if (kind <= 0) { + if (0x7b == w83793_read_value(client, W83793_REG_CHIPID)) { + kind = w83793; + } else { + if (kind == 0) + dev_warn(&adapter->dev, "w83793: Ignoring " + "'force' parameter for unknown chip " + "at address 0x%02x\n", address); + err = -ENODEV; + goto free_mem; + } + } + + /* Fill in the remaining client fields and put into the global list */ + strlcpy(client->name, "w83793", I2C_NAME_SIZE); + + mutex_init(&data->update_lock); + + /* Tell the I2C layer a new client has arrived */ + if ((err = i2c_attach_client(client))) + goto free_mem; + + if ((err = w83793_detect_subclients(adapter, address, kind, client))) + goto detach_client; + + /* Initialize the chip */ + w83793_init_client(client); + + data->vrm = vid_which_vrm(); + /* + Only fan 1-5 has their own input pins, + Pwm 1-3 has their own pins + */ + data->has_fan = 0x1f; + data->has_pwm = 0x07; + tmp = w83793_read_value(client, W83793_REG_MFC); + val = w83793_read_value(client, W83793_REG_FANIN_CTRL); + + /* check the function of pins 49-56 */ + if (!(tmp & 0x80)) { + data->has_pwm |= 0x18; /* pwm 4,5 */ + if (val & 0x01) { /* fan 6 */ + data->has_fan |= 0x20; + data->has_pwm |= 0x20; + } + if (val & 0x02) { /* fan 7 */ + data->has_fan |= 0x40; + data->has_pwm |= 0x40; + } + if (!(tmp & 0x40) && (val & 0x04)) { /* fan 8 */ + data->has_fan |= 0x80; + data->has_pwm |= 0x80; + } + } + + if (0x08 == (tmp & 0x0c)) { + if (val & 0x08) /* fan 9 */ + data->has_fan |= 0x100; + if (val & 0x10) /* fan 10 */ + data->has_fan |= 0x200; + } + + if (0x20 == (tmp & 0x30)) { + if (val & 0x20) /* fan 11 */ + data->has_fan |= 0x400; + if (val & 0x40) /* fan 12 */ + data->has_fan |= 0x800; + } + + if ((tmp & 0x01) && (val & 0x04)) { /* fan 8, second location */ + data->has_fan |= 0x80; + data->has_pwm |= 0x80; + } + + /* Register sysfs hooks */ + for (i = 0; i < ARRAY_SIZE(w83793_sensor_attr_2); i++) { + err = device_create_file(dev, + &w83793_sensor_attr_2[i].dev_attr); + if (err) + goto exit_remove; + } + + for (i = 0; i < ARRAY_SIZE(sda_single_files); i++) { + err = device_create_file(dev, &sda_single_files[i].dev_attr); + if (err) + goto exit_remove; + + } + + for (i = 5; i < 12; i++) { + int j; + if (!(data->has_fan & (1 << i))) + continue; + for (j = 0; j < files_fan; j++) { + err = device_create_file(dev, + &w83793_left_fan[(i - 5) * files_fan + + j].dev_attr); + if (err) + goto exit_remove; + } + } + + for (i = 3; i < 8; i++) { + int j; + if (!(data->has_pwm & (1 << i))) + continue; + for (j = 0; j < files_pwm; j++) { + err = device_create_file(dev, + &w83793_left_pwm[(i - 3) * files_pwm + + j].dev_attr); + if (err) + goto exit_remove; + } + } + + data->class_dev = hwmon_device_register(dev); + if (IS_ERR(data->class_dev)) { + err = PTR_ERR(data->class_dev); + goto exit_remove; + } + + return 0; + + /* Unregister sysfs hooks */ + +exit_remove: + for (i = 0; i < ARRAY_SIZE(w83793_sensor_attr_2); i++) + device_remove_file(dev, &w83793_sensor_attr_2[i].dev_attr); + + for (i = 0; i < ARRAY_SIZE(sda_single_files); i++) + device_remove_file(dev, &sda_single_files[i].dev_attr); + + for (i = 0; i < ARRAY_SIZE(w83793_left_fan); i++) + device_remove_file(dev, &w83793_left_fan[i].dev_attr); + + for (i = 0; i < ARRAY_SIZE(w83793_left_pwm); i++) + device_remove_file(dev, &w83793_left_pwm[i].dev_attr); + + if (data->lm75[0] != NULL) { + i2c_detach_client(data->lm75[0]); + kfree(data->lm75[0]); + } + if (data->lm75[1] != NULL) { + i2c_detach_client(data->lm75[1]); + kfree(data->lm75[1]); + } +detach_client: + i2c_detach_client(client); +free_mem: + kfree(data); +exit: + return err; +} + +static void w83793_update_nonvolatile(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83793_data *data = i2c_get_clientdata(client); + int i, j; + /* + They are somewhat "stable" registers, and to update them everytime + takes so much time, it's just not worthy. Update them in a long + interval to avoid exception. + */ + if (!(time_after(jiffies, data->last_nonvolatile + HZ * 300) + || !data->valid)) + return; + /* update voltage limits */ + for (i = 1; i < 3; i++) { + for (j = 0; j < ARRAY_SIZE(data->in); j++) { + data->in[j][i] = + w83793_read_value(client, W83793_REG_IN[j][i]); + } + data->in_low_bits[i] = + w83793_read_value(client, W83793_REG_IN_LOW_BITS[i]); + } + + for (i = 0; i < ARRAY_SIZE(data->fan_min); i++) { + /* Update the Fan measured value and limits */ + if (!(data->has_fan & (1 << i))) { + continue; + } + data->fan_min[i] = + w83793_read_value(client, W83793_REG_FAN_MIN(i)) << 8; + data->fan_min[i] |= + w83793_read_value(client, W83793_REG_FAN_MIN(i) + 1); + } + + for (i = 0; i < ARRAY_SIZE(data->temp_fan_map); i++) { + data->temp_fan_map[i] = + w83793_read_value(client, W83793_REG_TEMP_FAN_MAP(i)); + for (j = 1; j < 5; j++) { + data->temp[i][j] = + w83793_read_value(client, W83793_REG_TEMP[i][j]); + } + data->temp_cruise[i] = + w83793_read_value(client, W83793_REG_TEMP_CRUISE(i)); + for (j = 0; j < 7; j++) { + data->sf2_pwm[i][j] = + w83793_read_value(client, W83793_REG_SF2_PWM(i, j)); + data->sf2_temp[i][j] = + w83793_read_value(client, + W83793_REG_SF2_TEMP(i, j)); + } + } + + for (i = 0; i < ARRAY_SIZE(data->temp_mode); i++) + data->temp_mode[i] = + w83793_read_value(client, W83793_REG_TEMP_MODE[i]); + + for (i = 0; i < ARRAY_SIZE(data->tolerance); i++) { + data->tolerance[i] = + w83793_read_value(client, W83793_REG_TEMP_TOL(i)); + } + + for (i = 0; i < ARRAY_SIZE(data->pwm); i++) { + if (!(data->has_pwm & (1 << i))) + continue; + data->pwm[i][PWM_NONSTOP] = + w83793_read_value(client, W83793_REG_PWM(i, PWM_NONSTOP)); + data->pwm[i][PWM_START] = + w83793_read_value(client, W83793_REG_PWM(i, PWM_START)); + data->pwm_stop_time[i] = + w83793_read_value(client, W83793_REG_PWM_STOP_TIME(i)); + } + + data->pwm_default = w83793_read_value(client, W83793_REG_PWM_DEFAULT); + data->pwm_enable = w83793_read_value(client, W83793_REG_PWM_ENABLE); + data->pwm_uptime = w83793_read_value(client, W83793_REG_PWM_UPTIME); + data->pwm_downtime = w83793_read_value(client, W83793_REG_PWM_DOWNTIME); + data->temp_critical = + w83793_read_value(client, W83793_REG_TEMP_CRITICAL); + data->beep_enable = w83793_read_value(client, W83793_REG_OVT_BEEP); + + for (i = 0; i < ARRAY_SIZE(data->beeps); i++) { + data->beeps[i] = w83793_read_value(client, W83793_REG_BEEP(i)); + } + + data->last_nonvolatile = jiffies; +} + +static struct w83793_data *w83793_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83793_data *data = i2c_get_clientdata(client); + int i; + + mutex_lock(&data->update_lock); + + if (!(time_after(jiffies, data->last_updated + HZ * 2) + || !data->valid)) + goto END; + + /* Update the voltages measured value and limits */ + for (i = 0; i < ARRAY_SIZE(data->in); i++) + data->in[i][IN_READ] = + w83793_read_value(client, W83793_REG_IN[i][IN_READ]); + + data->in_low_bits[IN_READ] = + w83793_read_value(client, W83793_REG_IN_LOW_BITS[IN_READ]); + + for (i = 0; i < ARRAY_SIZE(data->fan); i++) { + if (!(data->has_fan & (1 << i))) { + continue; + } + data->fan[i] = + w83793_read_value(client, W83793_REG_FAN(i)) << 8; + data->fan[i] |= + w83793_read_value(client, W83793_REG_FAN(i) + 1); + } + + for (i = 0; i < ARRAY_SIZE(data->temp); i++) + data->temp[i][TEMP_READ] = + w83793_read_value(client, W83793_REG_TEMP[i][TEMP_READ]); + + data->temp_low_bits = + w83793_read_value(client, W83793_REG_TEMP_LOW_BITS); + + for (i = 0; i < ARRAY_SIZE(data->pwm); i++) { + if (data->has_pwm & (1 << i)) + data->pwm[i][PWM_DUTY] = + w83793_read_value(client, + W83793_REG_PWM(i, PWM_DUTY)); + } + + for (i = 0; i < ARRAY_SIZE(data->alarms); i++) + data->alarms[i] = + w83793_read_value(client, W83793_REG_ALARM(i)); + data->vid[0] = w83793_read_value(client, W83793_REG_VID_INA); + data->vid[1] = w83793_read_value(client, W83793_REG_VID_INB); + w83793_update_nonvolatile(dev); + data->last_updated = jiffies; + data->valid = 1; + +END: + mutex_unlock(&data->update_lock); + return data; +} + +/* Ignore the possibility that somebody change bank outside the driver + Must be called with data->update_lock held, except during initialization */ +static u8 w83793_read_value(struct i2c_client *client, u16 reg) +{ + struct w83793_data *data = i2c_get_clientdata(client); + u8 res = 0xff; + u8 new_bank = reg >> 8; + + new_bank |= data->bank & 0xfc; + if (data->bank != new_bank) { + if (i2c_smbus_write_byte_data + (client, W83793_REG_BANKSEL, new_bank) >= 0) + data->bank = new_bank; + else { + dev_err(&client->dev, + "set bank to %d failed, fall back " + "to bank %d, read reg 0x%x error\n", + new_bank, data->bank, reg); + res = 0x0; /* read 0x0 from the chip */ + goto END; + } + } + res = i2c_smbus_read_byte_data(client, reg & 0xff); +END: + return res; +} + +/* Must be called with data->update_lock held, except during initialization */ +static int w83793_write_value(struct i2c_client *client, u16 reg, u8 value) +{ + struct w83793_data *data = i2c_get_clientdata(client); + int res; + u8 new_bank = reg >> 8; + + new_bank |= data->bank & 0xfc; + if (data->bank != new_bank) { + if ((res = i2c_smbus_write_byte_data + (client, W83793_REG_BANKSEL, new_bank)) >= 0) + data->bank = new_bank; + else { + dev_err(&client->dev, + "set bank to %d failed, fall back " + "to bank %d, write reg 0x%x error\n", + new_bank, data->bank, reg); + goto END; + } + } + + res = i2c_smbus_write_byte_data(client, reg & 0xff, value); +END: + return res; +} + +static int __init sensors_w83793_init(void) +{ + return i2c_add_driver(&w83793_driver); +} + +static void __exit sensors_w83793_exit(void) +{ + i2c_del_driver(&w83793_driver); +} + +MODULE_AUTHOR("Yuan Mu"); +MODULE_DESCRIPTION("w83793 driver"); +MODULE_LICENSE("GPL"); + +module_init(sensors_w83793_init); +module_exit(sensors_w83793_exit); |