The patch titled hwmon: driver for Texas Instruments amc6821 chip has been added to the -mm tree. Its filename is hwmon-driver-for-texas-instruments-amc6821-chip.patch Before you just go and hit "reply", please: a) Consider who else should be cc'ed b) Prefer to cc a suitable mailing list as well c) Ideally: find the original patch on the mailing list and do a reply-to-all to that, adding suitable additional cc's *** Remember to use Documentation/SubmitChecklist when testing your code *** See http://userweb.kernel.org/~akpm/stuff/added-to-mm.txt to find out what to do about this The current -mm tree may be found at http://userweb.kernel.org/~akpm/mmotm/ ------------------------------------------------------ Subject: hwmon: driver for Texas Instruments amc6821 chip From: <tomaz.mertelj@xxxxxxxxxxxxxx> Signed-off-by: <tomaz.mertelj@xxxxxxxxxxxxxx> Cc: Jean Delvare <khali@xxxxxxxxxxxx> Signed-off-by: Andrew Morton <akpm@xxxxxxxxxxxxxxxxxxxx> --- Documentation/hwmon/amc6821 | 101 +++ drivers/hwmon/Kconfig | 10 drivers/hwmon/Makefile | 1 drivers/hwmon/amc6821.c | 1037 ++++++++++++++++++++++++++++++++++ 4 files changed, 1149 insertions(+) diff -puN /dev/null Documentation/hwmon/amc6821 --- /dev/null +++ a/Documentation/hwmon/amc6821 @@ -0,0 +1,101 @@ +Kernel driver amc6821 +===================== + +Supported chips: + Prefix: 'amc6821' + Addresses scanned: 0x18, 0x19, 0x1a, 0x2c, 0x2d, 0x2e, 0x4c, 0x4d, 0x4e + Datasheet: http://focus.ti.com/docs/prod/folders/print/amc6821.html + +Authors: + Tomaz Mertelj <tomaz.mertelj@xxxxxxxxxxxxxx> + + +Description +----------- + +This driver implements support for the Texas Instruments amc6821 chip. +The chip has one on-chip and one remote temperature sensor and one pwm fan +regulator. +The pwm can be controlled either from software or automatically. + +The driver provides the following sensor accesses in sysfs: + +temp1_input ro on-chip temperature +temp1_min rw " +temp1_max rw " +temp1_crit rw " +temp1_min_alarm ro " +temp1_max_alarm ro " +temp1_crit_alarm ro " + +temp2_input ro remote temperature +temp2_min rw " +temp2_max rw " +temp2_crit rw " +temp2_min_alarm ro " +temp2_max_alarm ro " +temp2_crit_alarm ro " +temp2_fault ro " + +fan1_input ro tachometer speed +fan1_min rw " +fan1_max rw " +fan1_fault ro " +fan1_div rw Fan divisor can be either 2 or 4. + +pwm1 rw pwm1 +pwm1_enable rw regulator mode, 1=open loop, 2=fan controlled + by remote temperature, 3=fan controlled by + combination of on-chip temperature and + remote-sensor temperature, +pwm1_auto_channels_temp ro 1 if pwm_enable==2, 3 if pwm_enable==3 +pwm1_auto_point1_pwm ro Hardwired to 0, shared for both + temperature channels. +pwm1_auto_point2_pwm rw This value, shared for both temperature + channels. +pwm1_auto_point3_pwm rw Hardwired to 255, shared for both + temperature channels. + +temp1_auto_point1_temp ro Hardwired to temp2_auto_point1_temp + which is rw. Below this temperature fan stops. +temp1_auto_point2_temp rw The low-temperature limit of the proportional + range. Below this temperature + pwm1 = pwm1_auto_point2_pwm. It can go from + 0 degree C and 124 degree C in steps of + 4 degree C. Read it out after writing to get + actual value. +temp1_auto_point3_temp rw Above this temperature fan runs at maximum + speed. It can go from temp1_auto_point2_temp. + It can only have certain discrete values + which depend on temp1_auto_point2_temp and + pwm1_auto_point2_pwm. Read it out after + writing to get the actual value. + +temp2_auto_point1_temp rw Must be between 0 degree C and 63 degree C and + it defines passive cooling temperature. + Below this temperature the fan stops in + the closed loop mode. +temp2_auto_point2_temp rw The low-temperature limit of the proportional + range. Below this temperature + pwm1 = pwm1_auto_point2_pwm. It can go from + 0 degree C and 124 degree C in steps + of 4 degree C. + +temp2_auto_point3_temp rw Above this temperature fan runs at maximum + speed. It can only have certain discrete + values which depend on temp2_auto_point2_temp + and pwm1_auto_point2_pwm. Read it out after + writing to get actual value. + + +Module parameters +----------------- + +If your board has a BIOS that initializes the amc6821 correctly, you should +load the module with: init=0. + +If your board BIOS doesn't initialize the chip, or you want +different settings, you can set the following parameters: +init=1, +pwminv: 0 default pwm output, 1 inverts pwm output. + diff -puN drivers/hwmon/Kconfig~hwmon-driver-for-texas-instruments-amc6821-chip drivers/hwmon/Kconfig --- a/drivers/hwmon/Kconfig~hwmon-driver-for-texas-instruments-amc6821-chip +++ a/drivers/hwmon/Kconfig @@ -794,6 +794,16 @@ config SENSORS_ADS7828 This driver can also be built as a module. If so, the module will be called ads7828. +config SENSORS_AMC6821 + tristate "Texas Instruments AMC6821" + depends on I2C && EXPERIMENTAL + help + If you say yes here you get support for the Texas Instruments + AMC6821 hardware monitoring chips. + + This driver can also be build as a module. If so, the module + will be called amc6821. + config SENSORS_THMC50 tristate "Texas Instruments THMC50 / Analog Devices ADM1022" depends on I2C && EXPERIMENTAL diff -puN drivers/hwmon/Makefile~hwmon-driver-for-texas-instruments-amc6821-chip drivers/hwmon/Makefile --- a/drivers/hwmon/Makefile~hwmon-driver-for-texas-instruments-amc6821-chip +++ a/drivers/hwmon/Makefile @@ -82,6 +82,7 @@ obj-$(CONFIG_SENSORS_SIS5595) += sis5595 obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o +obj-$(CONFIG_SENSORS_AMC6821) += amc6821.o obj-$(CONFIG_SENSORS_THMC50) += thmc50.o obj-$(CONFIG_SENSORS_TMP401) += tmp401.o obj-$(CONFIG_SENSORS_TMP421) += tmp421.o diff -puN /dev/null drivers/hwmon/amc6821.c --- /dev/null +++ a/drivers/hwmon/amc6821.c @@ -0,0 +1,1037 @@ +/* + amc6821.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (C) 2009 T. Mertelj <tomaz.mertelj@xxxxxxxxxxxxxx> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + + +#include <linux/kernel.h> /* Needed for KERN_INFO */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/jiffies.h> +#include <linux/i2c.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/err.h> +#include <linux/mutex.h> + + +/* + * Addresses to scan. + */ + +static const unsigned short normal_i2c[] = {0x18, 0x19, 0x1a, 0x2c, 0x2d, 0x2e, + 0x4c, 0x4d, 0x4e, I2C_CLIENT_END}; + + + +/* + * Insmod parameters + */ + +static int pwminv = 0; /*Inverted PWM output. */ +module_param(pwminv, int, S_IRUGO); + +static int init = 1; /*Power-on initialization.*/ +module_param(init, int, S_IRUGO); + + +I2C_CLIENT_INSMOD_1(amc6821); + +#define AMC6821_REG_DEV_ID 0x3D +#define AMC6821_REG_COMP_ID 0x3E +#define AMC6821_REG_CONF1 0x00 +#define AMC6821_REG_CONF2 0x01 +#define AMC6821_REG_CONF3 0x3F +#define AMC6821_REG_CONF4 0x04 +#define AMC6821_REG_STAT1 0x02 +#define AMC6821_REG_STAT2 0x03 +#define AMC6821_REG_TDATA_LOW 0x08 +#define AMC6821_REG_TDATA_HI 0x09 +#define AMC6821_REG_LTEMP_HI 0x0A +#define AMC6821_REG_RTEMP_HI 0x0B +#define AMC6821_REG_LTEMP_LIMIT_MIN 0x15 +#define AMC6821_REG_LTEMP_LIMIT_MAX 0x14 +#define AMC6821_REG_RTEMP_LIMIT_MIN 0x19 +#define AMC6821_REG_RTEMP_LIMIT_MAX 0x18 +#define AMC6821_REG_LTEMP_CRIT 0x1B +#define AMC6821_REG_RTEMP_CRIT 0x1D +#define AMC6821_REG_PSV_TEMP 0x1C +#define AMC6821_REG_DCY 0x22 +#define AMC6821_REG_LTEMP_FAN_CTRL 0x24 +#define AMC6821_REG_RTEMP_FAN_CTRL 0x25 +#define AMC6821_REG_DCY_LOW_TEMP 0x21 + +#define AMC6821_REG_TACH_LLIMITL 0x10 +#define AMC6821_REG_TACH_LLIMITH 0x11 +#define AMC6821_REG_TACH_HLIMITL 0x12 +#define AMC6821_REG_TACH_HLIMITH 0x13 + +#define AMC6821_CONF1_START 0x01 +#define AMC6821_CONF1_FAN_INT_EN 0x02 +#define AMC6821_CONF1_FANIE 0x04 +#define AMC6821_CONF1_PWMINV 0x08 +#define AMC6821_CONF1_FAN_FAULT_EN 0x10 +#define AMC6821_CONF1_FDRC0 0x20 +#define AMC6821_CONF1_FDRC1 0x40 +#define AMC6821_CONF1_THERMOVIE 0x80 + +#define AMC6821_CONF2_PWM_EN 0x01 +#define AMC6821_CONF2_TACH_MODE 0x02 +#define AMC6821_CONF2_TACH_EN 0x04 +#define AMC6821_CONF2_RTFIE 0x08 +#define AMC6821_CONF2_LTOIE 0x10 +#define AMC6821_CONF2_RTOIE 0x20 +#define AMC6821_CONF2_PSVIE 0x40 +#define AMC6821_CONF2_RST 0x80 + +#define AMC6821_CONF3_THERM_FAN_EN 0x80 +#define AMC6821_CONF3_REV_MASK 0x0F + +#define AMC6821_CONF4_OVREN 0x10 +#define AMC6821_CONF4_TACH_FAST 0x20 +#define AMC6821_CONF4_PSPR 0x40 +#define AMC6821_CONF4_MODE 0x80 + +#define AMC6821_STAT1_RPM_ALARM 0x01 +#define AMC6821_STAT1_FANS 0x02 +#define AMC6821_STAT1_RTH 0x04 +#define AMC6821_STAT1_RTL 0x08 +#define AMC6821_STAT1_R_THERM 0x10 +#define AMC6821_STAT1_RTF 0x20 +#define AMC6821_STAT1_LTH 0x40 +#define AMC6821_STAT1_LTL 0x80 + +#define AMC6821_STAT2_RTC 0x08 +#define AMC6821_STAT2_LTC 0x10 +#define AMC6821_STAT2_LPSV 0x20 +#define AMC6821_STAT2_L_THERM 0x40 +#define AMC6821_STAT2_THERM_IN 0x80 + + +static int amc6821_probe( + struct i2c_client *client, + const struct i2c_device_id *id); +static int amc6821_detect( + struct i2c_client *client, + int kind, + struct i2c_board_info *info); +static int amc6821_init_client(struct i2c_client *client); +static int amc6821_remove(struct i2c_client *client); +static struct amc6821_data *amc6821_update_device(struct device *dev); + +/* + * Driver data (common to all clients) + */ + +static const struct i2c_device_id amc6821_id[] = { + { "amc6821", amc6821 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, amc6821_id); + +static struct i2c_driver amc6821_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "amc6821", + }, + .probe = amc6821_probe, + .remove = amc6821_remove, + .id_table = amc6821_id, + .detect = amc6821_detect, + .address_data = &addr_data, +}; + + +/* + * Client data (each client gets its own) + */ + +struct amc6821_data { + struct device *hwmon_dev; + struct mutex update_lock; + char valid; /* zero until following fields are valid */ + unsigned long last_updated; /* in jiffies */ + + /* register values */ + int temp1_input; + int temp1_min; + int temp1_max; + int temp1_crit; + + int temp2_input; + int temp2_min; + int temp2_max; + int temp2_crit; + + u16 fan1_input; + u16 fan1_min; + u16 fan1_max; + u8 fan1_div; + + u8 pwm1; + u8 temp1_auto_point_temp[3]; + u8 temp2_auto_point_temp[3]; + u8 pwm1_auto_point_pwm[3]; + u8 pwm1_enable; + u8 pwm1_auto_channels_temp; + + u8 stat1; + u8 stat2; +}; + + +#define get_temp_para(name) \ +static ssize_t get_##name(\ + struct device *dev,\ + struct device_attribute *devattr,\ + char *buf)\ +{\ + struct amc6821_data *data = amc6821_update_device(dev);\ + return sprintf(buf, "%d\n", data->name * 1000);\ +} + +get_temp_para(temp1_input); +get_temp_para(temp1_min); +get_temp_para(temp1_max); +get_temp_para(temp2_input); +get_temp_para(temp2_min); +get_temp_para(temp2_max); +get_temp_para(temp1_crit); +get_temp_para(temp2_crit); + +#define set_temp_para(name, reg)\ +static ssize_t set_##name(\ + struct device *dev,\ + struct device_attribute *attr,\ + const char *buf,\ + size_t count)\ +{ \ + struct i2c_client *client = to_i2c_client(dev); \ + struct amc6821_data *data = i2c_get_clientdata(client); \ + int val = simple_strtol(buf, NULL, 10); \ + \ + mutex_lock(&data->update_lock); \ + data->name = SENSORS_LIMIT(val / 1000, -128, 127); \ + if (i2c_smbus_write_byte_data(client, reg, data->name)) {\ + dev_err(&client->dev, "Register write error, aborting.\n");\ + count = -EIO;\ + } \ + mutex_unlock(&data->update_lock); \ + return count; \ +} + +set_temp_para(temp1_min, AMC6821_REG_LTEMP_LIMIT_MIN); +set_temp_para(temp1_max, AMC6821_REG_LTEMP_LIMIT_MAX); +set_temp_para(temp2_min, AMC6821_REG_RTEMP_LIMIT_MIN); +set_temp_para(temp2_max, AMC6821_REG_RTEMP_LIMIT_MAX); +set_temp_para(temp1_crit, AMC6821_REG_LTEMP_CRIT); +set_temp_para(temp2_crit, AMC6821_REG_RTEMP_CRIT); + +#define get_temp_alarm(name, reg, mask)\ +static ssize_t get_##name(\ + struct device *dev, \ + struct device_attribute *devattr,\ + char *buf)\ +{\ + struct amc6821_data *data = amc6821_update_device(dev);\ + if (data->reg & mask)\ + return sprintf(buf, "1");\ + else \ + return sprintf(buf, "0");\ +} \ + +get_temp_alarm(temp1_min_alarm, stat1, AMC6821_STAT1_LTL) +get_temp_alarm(temp1_max_alarm, stat1, AMC6821_STAT1_LTH) +get_temp_alarm(temp2_min_alarm, stat1, AMC6821_STAT1_RTL) +get_temp_alarm(temp2_max_alarm, stat1, AMC6821_STAT1_RTH) +get_temp_alarm(temp1_crit_alarm, stat2, AMC6821_STAT2_LTC) +get_temp_alarm(temp2_crit_alarm, stat2, AMC6821_STAT2_RTC) + + +static ssize_t get_temp2_fault( + struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct amc6821_data *data = amc6821_update_device(dev); + if (data->stat1 & AMC6821_STAT1_RTF) + return sprintf(buf, "1"); + else + return sprintf(buf, "0"); +} + +static ssize_t get_pwm1( + struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct amc6821_data *data = amc6821_update_device(dev); + return sprintf(buf, "%d\n", data->pwm1); +} + +static ssize_t set_pwm1( + struct device *dev, + struct device_attribute *devattr, + const char *buf, + size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct amc6821_data *data = i2c_get_clientdata(client); + int val = simple_strtol(buf, NULL, 10); + + mutex_lock(&data->update_lock); + data->pwm1 = SENSORS_LIMIT(val , 0, 255); + i2c_smbus_write_byte_data(client, AMC6821_REG_DCY, data->pwm1); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t get_pwm1_enable( + struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct amc6821_data *data = amc6821_update_device(dev); + return sprintf(buf, "%d\n", data->pwm1_enable); +} + +static ssize_t set_pwm1_enable( + struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct amc6821_data *data = i2c_get_clientdata(client); + int val = simple_strtol(buf, NULL, 10); + + int config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF1); + if (config < 0) { + dev_err(&client->dev, + "Error reading configuration register, aborting.\n"); + return -EIO; + } + + switch (val) { + case 1: + config &= ~AMC6821_CONF1_FDRC0; + config &= ~AMC6821_CONF1_FDRC1; + break; + case 2: + config &= ~AMC6821_CONF1_FDRC0; + config |= AMC6821_CONF1_FDRC1; + break; + case 3: + config |= AMC6821_CONF1_FDRC0; + config |= AMC6821_CONF1_FDRC1; + break; + default: + return -EINVAL; + } + mutex_lock(&data->update_lock); + if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF1, config)) { + dev_err(&client->dev, + "Configuration register write error, aborting.\n"); + count = -EIO; + } + mutex_unlock(&data->update_lock); + return count; +} + + +static ssize_t get_pwm1_auto_channels_temp( + struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct amc6821_data *data = amc6821_update_device(dev); + return sprintf(buf, "%d\n", data->pwm1_auto_channels_temp); +} + + +#define get_auto_point(name)\ +static ssize_t get_##name(\ + struct device *dev,\ + struct device_attribute *devattr,\ + char *buf)\ +{\ + int nr = to_sensor_dev_attr(devattr)->index;\ + struct amc6821_data *data = amc6821_update_device(dev);\ + return sprintf(buf, "%d\n", data->name[nr] * 1000);\ +} + +get_auto_point(temp1_auto_point_temp); +get_auto_point(temp2_auto_point_temp); + +static ssize_t get_pwm1_auto_point_pwm( + struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + int nr = to_sensor_dev_attr(devattr)->index; + struct amc6821_data *data = amc6821_update_device(dev); + return sprintf(buf, "%d\n", data->pwm1_auto_point_pwm[nr]); +} + + +#define set_temp_auto_point_temp(name, reg)\ +static ssize_t set_##name(\ + struct device *dev,\ + struct device_attribute *attr,\ + const char *buf,\ + size_t count)\ +{ \ + struct i2c_client *client = to_i2c_client(dev); \ + struct amc6821_data *data = amc6821_update_device(dev);\ + int nr = to_sensor_dev_attr(attr)->index;\ + int val = simple_strtol(buf, NULL, 10); \ + u8 tmp;\ + int dt;\ + int dpwm;\ + \ + mutex_lock(&data->update_lock); \ + switch (nr) {\ + case 0:\ + data->name[0] = SENSORS_LIMIT(val / 1000, 0,\ + data->temp1_auto_point_temp[1]);\ + data->name[0] = SENSORS_LIMIT(data->name[0], 0,\ + data->temp2_auto_point_temp[1]);\ + data->name[0] = SENSORS_LIMIT(data->name[0], 0, 63); \ + data->valid = 0;\ + if (i2c_smbus_write_byte_data(\ + client,\ + AMC6821_REG_PSV_TEMP,\ + data->name[0])) {\ + dev_err(&client->dev,\ + "Register write error, aborting.\n");\ + count = -EIO;\ + } \ + goto EXIT;\ + break;\ + case 1:\ + data->name[1] = SENSORS_LIMIT(\ + val / 1000,\ + (data->name[0] & 0x7C) + 4,\ + 124);\ + data->name[1] &= 0x7C;\ + data->name[2] = SENSORS_LIMIT(\ + data->name[2], data->name[1] + 1,\ + 255);\ + break;\ + case 2:\ + data->name[2] = SENSORS_LIMIT(\ + val / 1000,\ + data->name[1]+1,\ + 255); \ + break;\ + } \ + dt = data->name[2]-data->name[1];\ + dpwm = data->pwm1_auto_point_pwm[2] - data->pwm1_auto_point_pwm[1];\ + for (tmp = 4; tmp > 0; tmp--) {\ + if (dt * (0x20 >> tmp) >= dpwm)\ + break;\ + } \ + tmp |= (data->name[1] & 0x7C) << 1;\ + if (i2c_smbus_write_byte_data(client, reg, tmp)) {\ + dev_err(&client->dev, "Register write error, aborting.\n");\ + count = -EIO;\ + } \ + data->valid = 0;\ +EXIT:\ + mutex_unlock(&data->update_lock); \ + return count; \ +} + +set_temp_auto_point_temp(temp1_auto_point_temp, AMC6821_REG_LTEMP_FAN_CTRL); +set_temp_auto_point_temp(temp2_auto_point_temp, AMC6821_REG_RTEMP_FAN_CTRL); + +static ssize_t set_pwm1_auto_point_pwm( + struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct amc6821_data *data = i2c_get_clientdata(client); + int val = simple_strtol(buf, NULL, 10); + u8 tmp; + int dt; + int dpwm; + + mutex_lock(&data->update_lock); + data->pwm1_auto_point_pwm[1] = SENSORS_LIMIT(val, 0, 254); + if (i2c_smbus_write_byte_data(client, AMC6821_REG_DCY_LOW_TEMP, + data->pwm1_auto_point_pwm[1])) { + dev_err(&client->dev, "Register write error, aborting.\n"); + count = -EIO; + } + + dpwm = data->pwm1_auto_point_pwm[2] - data->pwm1_auto_point_pwm[1]; + dt = data->temp1_auto_point_temp[2]-data->temp1_auto_point_temp[1]; + for (tmp = 4; tmp > 0; tmp--) { + if (dt * (0x20 >> tmp) >= dpwm) + break; + } + tmp |= (data->temp1_auto_point_temp[1] & 0x7C) << 1; + if (i2c_smbus_write_byte_data(client, + AMC6821_REG_LTEMP_FAN_CTRL, tmp)) { + dev_err(&client->dev, "Register write error, aborting.\n"); + count = -EIO; + } + + dt = data->temp2_auto_point_temp[2]-data->temp2_auto_point_temp[1]; + for (tmp = 4; tmp > 0; tmp--) { + if (dt * (0x20 >> tmp) >= dpwm) + break; + } + tmp |= (data->temp2_auto_point_temp[1] & 0x7C) << 1; + if (i2c_smbus_write_byte_data(client, + AMC6821_REG_RTEMP_FAN_CTRL, tmp)) { + dev_err(&client->dev, "Register write error, aborting.\n"); + count = -EIO; + } + data->valid = 0; + mutex_unlock(&data->update_lock); + return count; +} + + +#define get_fan_para(name) static ssize_t get_##name(\ + struct device *dev,\ + struct device_attribute *devattr,\ + char *buf)\ +{ \ + struct amc6821_data *data = amc6821_update_device(dev); \ + if (0 == data->name)\ + return sprintf(buf, "0");\ + return sprintf(buf, "%d\n", (int)(6000000 / data->name)); \ +} + +get_fan_para(fan1_input); +get_fan_para(fan1_min); +get_fan_para(fan1_max); + +static ssize_t get_fan1_fault( + struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct amc6821_data *data = amc6821_update_device(dev); + if (data->stat1 & AMC6821_STAT1_FANS) + return sprintf(buf, "1"); + else + return sprintf(buf, "0"); +} + + +#define set_fan_para(name, reg) \ +static ssize_t set_##name(\ + struct device *dev,\ + struct device_attribute *attr,\ + const char *buf, size_t count)\ +{ \ + struct i2c_client *client = to_i2c_client(dev); \ + struct amc6821_data *data = i2c_get_clientdata(client); \ + int val = simple_strtol(buf, NULL, 10); \ + val = 1 > val ? 0xFFFF : 6000000/val; \ +\ + mutex_lock(&data->update_lock); \ + data->name = (u16) SENSORS_LIMIT(val, 1, 0xFFFF); \ + if (i2c_smbus_write_byte_data(client, reg, data->name & 0xFF)) { \ + dev_err(&client->dev, "Register write error, aborting.\n");\ + count = -EIO;\ + } \ + if (i2c_smbus_write_byte_data(client, reg+1, data->name >> 8)) { \ + dev_err(&client->dev, "Register write error, aborting.\n");\ + count = -EIO;\ + } \ + mutex_unlock(&data->update_lock); \ + return count; \ +} + + +set_fan_para(fan1_min, AMC6821_REG_TACH_LLIMITL); +set_fan_para(fan1_max, AMC6821_REG_TACH_HLIMITL); + + +static ssize_t get_fan1_div( + struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct amc6821_data *data = amc6821_update_device(dev); + return sprintf(buf, "%d\n", data->fan1_div); +} + +static ssize_t set_fan1_div( + struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct amc6821_data *data = i2c_get_clientdata(client); + int val = simple_strtol(buf, NULL, 10); + int config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF4); + + if (config < 0) { + dev_err(&client->dev, + "Error reading configuration register, aborting.\n"); + return -EIO; + } + mutex_lock(&data->update_lock); + switch (val) { + case 2: + config &= ~AMC6821_CONF4_PSPR; + data->fan1_div = 2; + break; + case 4: + config |= AMC6821_CONF4_PSPR; + data->fan1_div = 4; + break; + default: + mutex_unlock(&data->update_lock); + return -EINVAL; + } + if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF4, config)) { + dev_err(&client->dev, + "Configuration register write error, aborting.\n"); + count = -EIO; + } + mutex_unlock(&data->update_lock); + return count; +} + + + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, get_temp1_input, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_min, S_IRUGO | S_IWUSR, get_temp1_min, + set_temp1_min, 0); +static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO | S_IWUSR, get_temp1_max, + set_temp1_max, 0); +static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO | S_IWUSR, get_temp1_crit, + set_temp1_crit, 0); +static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, + get_temp1_min_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, + get_temp1_max_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, + get_temp1_crit_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO | S_IWUSR, + get_temp2_input, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_min, S_IRUGO | S_IWUSR, get_temp2_min, + set_temp2_min, 0); +static SENSOR_DEVICE_ATTR(temp2_max, S_IRUGO | S_IWUSR, get_temp2_max, + set_temp2_max, 0); +static SENSOR_DEVICE_ATTR(temp2_crit, S_IRUGO | S_IWUSR, get_temp2_crit, + set_temp2_crit, 0); +static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, + get_temp2_fault, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO, + get_temp2_min_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, + get_temp2_max_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_crit_alarm, S_IRUGO, + get_temp2_crit_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, get_fan1_input, NULL, 0); +static SENSOR_DEVICE_ATTR(fan1_min, S_IRUGO | S_IWUSR, + get_fan1_min, set_fan1_min, 0); +static SENSOR_DEVICE_ATTR(fan1_max, S_IRUGO | S_IWUSR, + get_fan1_max, set_fan1_max, 0); +static SENSOR_DEVICE_ATTR(fan1_fault, S_IRUGO, get_fan1_fault, NULL, 0); +static SENSOR_DEVICE_ATTR(fan1_div, S_IRUGO | S_IWUSR, + get_fan1_div, set_fan1_div, 0); + +static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, get_pwm1, set_pwm1, 0); +static SENSOR_DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO, + get_pwm1_enable, set_pwm1_enable, 0); +static SENSOR_DEVICE_ATTR(pwm1_auto_point1_pwm, S_IRUGO, + get_pwm1_auto_point_pwm, NULL, 0); +static SENSOR_DEVICE_ATTR(pwm1_auto_point2_pwm, S_IWUSR | S_IRUGO, + get_pwm1_auto_point_pwm, set_pwm1_auto_point_pwm, 1); +static SENSOR_DEVICE_ATTR(pwm1_auto_point3_pwm, S_IRUGO, + get_pwm1_auto_point_pwm, NULL, 2); +static SENSOR_DEVICE_ATTR(pwm1_auto_channels_temp, S_IRUGO, + get_pwm1_auto_channels_temp, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_auto_point1_temp, S_IRUGO, + get_temp1_auto_point_temp, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_auto_point2_temp, S_IWUSR | S_IRUGO, + get_temp1_auto_point_temp, set_temp1_auto_point_temp, 1); +static SENSOR_DEVICE_ATTR(temp1_auto_point3_temp, S_IWUSR | S_IRUGO, + get_temp1_auto_point_temp, set_temp1_auto_point_temp, 2); + +static SENSOR_DEVICE_ATTR(temp2_auto_point1_temp, S_IWUSR | S_IRUGO, + get_temp2_auto_point_temp, set_temp2_auto_point_temp, 0); +static SENSOR_DEVICE_ATTR(temp2_auto_point2_temp, S_IWUSR | S_IRUGO, + get_temp2_auto_point_temp, set_temp2_auto_point_temp, 1); +static SENSOR_DEVICE_ATTR(temp2_auto_point3_temp, S_IWUSR | S_IRUGO, + get_temp2_auto_point_temp, set_temp2_auto_point_temp, 2); + + + +static struct attribute *amc6821_attrs[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_crit.dev_attr.attr, + &sensor_dev_attr_temp2_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_fault.dev_attr.attr, + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan1_max.dev_attr.attr, + &sensor_dev_attr_fan1_fault.dev_attr.attr, + &sensor_dev_attr_fan1_div.dev_attr.attr, + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_pwm1_enable.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_channels_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point3_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point3_temp.dev_attr.attr, + NULL +}; + +static struct attribute_group amc6821_attr_grp = { + .attrs = amc6821_attrs, +}; + + + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int amc6821_detect( + struct i2c_client *client, + int kind, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + int address = client->addr; + + dev_dbg(&adapter->dev, "amc6821_detect called, kind = %d\n", kind); + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_dbg(&adapter->dev, + "max6650: I2C bus doesn't support byte read mode, skipping.\n"); + return -ENODEV; + } + if ((kind < 0) && + ((i2c_smbus_read_byte_data(client, AMC6821_REG_DEV_ID) & + 0xDE) || + (i2c_smbus_read_byte_data(client, AMC6821_REG_COMP_ID) & + 0xB6))) { + dev_dbg(&adapter->dev, + "amc6821: detection failed at 0x%02x.\n", + address); + return -ENODEV; + } + + dev_info(&adapter->dev, "amc6821: chip found at 0x%02x.\n", address); + strlcpy(info->type, "amc6821", I2C_NAME_SIZE); + + return 0; +} + +static int amc6821_probe( + struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct amc6821_data *data; + int err; + + data = kzalloc(sizeof(struct amc6821_data), GFP_KERNEL); + if (!data) { + dev_err(&client->dev, "out of memory.\n"); + return -ENOMEM; + } + + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + /* + * Initialize the amc6821 chip + */ + err = amc6821_init_client(client); + if (err) + goto err_free; + + err = sysfs_create_group(&client->dev.kobj, &amc6821_attr_grp); + if (err) + goto err_free; + + data->hwmon_dev = hwmon_device_register(&client->dev); + if (!IS_ERR(data->hwmon_dev)) + return 0; + + err = PTR_ERR(data->hwmon_dev); + dev_err(&client->dev, "error registering hwmon device.\n"); + sysfs_remove_group(&client->dev.kobj, &amc6821_attr_grp); +err_free: + kfree(data); + return err; +} + +static int amc6821_remove(struct i2c_client *client) +{ + struct amc6821_data *data = i2c_get_clientdata(client); + + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, &amc6821_attr_grp); + + kfree(data); + + return 0; +} + + +static int amc6821_init_client(struct i2c_client *client) +{ + int config; + int err = -EIO; + + if (init) { + config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF4); + + if (config < 0) { + dev_err(&client->dev, + "Error reading configuration register, aborting.\n"); + return err; + } + + config |= AMC6821_CONF4_MODE; + + if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF4, + config)) { + dev_err(&client->dev, + "Configuration register write error, aborting.\n"); + return err; + } + + config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF3); + + if (config < 0) { + dev_err(&client->dev, + "Error reading configuration register, aborting.\n"); + return err; + } + + config &= ~AMC6821_CONF3_THERM_FAN_EN; + + if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF3, + config)) { + dev_err(&client->dev, + "Configuration register write error, aborting.\n"); + return err; + } + + config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF2); + + if (config < 0) { + dev_err(&client->dev, + "Error reading configuration register, aborting.\n"); + return err; + } + + config &= ~AMC6821_CONF2_RTFIE; + config &= ~AMC6821_CONF2_LTOIE; + config &= ~AMC6821_CONF2_RTOIE; + if (i2c_smbus_write_byte_data(client, + AMC6821_REG_CONF2, config)) { + dev_err(&client->dev, + "Configuration register write error, aborting.\n"); + return err; + } + + config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF1); + + if (config < 0) { + dev_err(&client->dev, + "Error reading configuration register, aborting.\n"); + return err; + } + + config &= ~AMC6821_CONF1_THERMOVIE; + config &= ~AMC6821_CONF1_FANIE; + config |= AMC6821_CONF1_START; + if (pwminv) + config |= AMC6821_CONF1_PWMINV; + else + config &= ~AMC6821_CONF1_PWMINV; + + if (i2c_smbus_write_byte_data( + client, AMC6821_REG_CONF1, config)) { + dev_err(&client->dev, + "Configuration register write error, aborting.\n"); + return err; + } + } + return 0; +} + + +static struct amc6821_data *amc6821_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct amc6821_data *data = i2c_get_clientdata(client); + int timeout = HZ; + u8 reg; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + timeout) || + !data->valid) { + data->temp1_input = i2c_smbus_read_byte_data(client, + AMC6821_REG_LTEMP_HI); + data->temp1_min = i2c_smbus_read_byte_data(client, + AMC6821_REG_LTEMP_LIMIT_MIN); + data->temp1_max = i2c_smbus_read_byte_data(client, + AMC6821_REG_LTEMP_LIMIT_MAX); + data->temp1_crit = i2c_smbus_read_byte_data(client, + AMC6821_REG_LTEMP_CRIT); + + data->temp2_input = i2c_smbus_read_byte_data(client, + AMC6821_REG_RTEMP_HI); + data->temp2_min = i2c_smbus_read_byte_data(client, + AMC6821_REG_RTEMP_LIMIT_MIN); + data->temp2_max = i2c_smbus_read_byte_data(client, + AMC6821_REG_RTEMP_LIMIT_MAX); + data->temp2_crit = i2c_smbus_read_byte_data(client, + AMC6821_REG_RTEMP_CRIT); + + data->stat1 = i2c_smbus_read_byte_data(client, + AMC6821_REG_STAT1); + data->stat2 = i2c_smbus_read_byte_data(client, + AMC6821_REG_STAT2); + + data->pwm1 = i2c_smbus_read_byte_data(client, + AMC6821_REG_DCY); + data->fan1_input = i2c_smbus_read_byte_data(client, + AMC6821_REG_TDATA_LOW); + data->fan1_input += i2c_smbus_read_byte_data(client, + AMC6821_REG_TDATA_HI) << 8; + data->fan1_min = i2c_smbus_read_byte_data(client, + AMC6821_REG_TACH_LLIMITL); + data->fan1_min += i2c_smbus_read_byte_data(client, + AMC6821_REG_TACH_LLIMITL+1) << 8; + data->fan1_max = i2c_smbus_read_byte_data(client, + AMC6821_REG_TACH_HLIMITL); + data->fan1_max += i2c_smbus_read_byte_data(client, + AMC6821_REG_TACH_HLIMITL+1) << 8; + data->fan1_div = i2c_smbus_read_byte_data(client, + AMC6821_REG_CONF4); + data->fan1_div = data->fan1_div & AMC6821_CONF4_PSPR ? 4 : 2; + + data->pwm1_auto_point_pwm[0] = 0; + data->pwm1_auto_point_pwm[2] = 255; + data->pwm1_auto_point_pwm[1] = i2c_smbus_read_byte_data(client, + AMC6821_REG_DCY_LOW_TEMP); + + data->temp1_auto_point_temp[0] = + i2c_smbus_read_byte_data(client, + AMC6821_REG_PSV_TEMP); + data->temp2_auto_point_temp[0] = + data->temp1_auto_point_temp[0]; + reg = i2c_smbus_read_byte_data(client, + AMC6821_REG_LTEMP_FAN_CTRL); + data->temp1_auto_point_temp[1] = (reg & 0xF8) >> 1; + reg &= 0x07; + reg = 0x20 >> reg; + if (reg > 0) + data->temp1_auto_point_temp[2] = + data->temp1_auto_point_temp[1] + + (data->pwm1_auto_point_pwm[2] - + data->pwm1_auto_point_pwm[1]) / reg; + else + data->temp1_auto_point_temp[2] = 255; + + reg = i2c_smbus_read_byte_data(client, + AMC6821_REG_RTEMP_FAN_CTRL); + data->temp2_auto_point_temp[1] = (reg & 0xF8) >> 1; + reg &= 0x07; + reg = 0x20 >> reg; + if (reg > 0) + data->temp2_auto_point_temp[2] = + data->temp2_auto_point_temp[1] + + (data->pwm1_auto_point_pwm[2] - + data->pwm1_auto_point_pwm[1]) / reg; + else + data->temp2_auto_point_temp[2] = 255; + + reg = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF1); + reg = (reg >> 5) & 0x3; + switch (reg) { + case 0: /*open loop: software sets pwm1*/ + data->pwm1_auto_channels_temp = 0; + data->pwm1_enable = 1; + break; + case 2: /*closed loop: remote T (temp2)*/ + data->pwm1_auto_channels_temp = 2; + data->pwm1_enable = 2; + break; + case 3: /*closed loop: local and remote T (temp2)*/ + data->pwm1_auto_channels_temp = 3; + data->pwm1_enable = 3; + break; + case 1: /*semi-open loop: software sets rpm, chip controls pwm1, + *currently not implemented + */ + data->pwm1_auto_channels_temp = 0; + data->pwm1_enable = 0; + break; + } + + data->last_updated = jiffies; + data->valid = 1; + } + mutex_unlock(&data->update_lock); + return data; +} + + +static int __init amc6821_init(void) +{ + return i2c_add_driver(&amc6821_driver); +} + +static void __exit amc6821_exit(void) +{ + i2c_del_driver(&amc6821_driver); +} + +module_init(amc6821_init); +module_exit(amc6821_exit); + + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("T. Mertelj <tomaz.mertelj@xxxxxxxxxxxxxx>"); +MODULE_DESCRIPTION("Texas Instruments amc6821 hwmon driver"); +MODULE_SUPPORTED_DEVICE("amc6821"); _ Patches currently in -mm which might be from tomaz.mertelj@xxxxxxxxxxxxxx are hwmon-driver-for-texas-instruments-amc6821-chip.patch -- To unsubscribe from this list: send the line "unsubscribe mm-commits" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html