13bcfa9e4SHerbert Valerio Riedel /* 21b9c491dSGuenter Roeck * g760a - Driver for the Global Mixed-mode Technology Inc. G760A 31b9c491dSGuenter Roeck * fan speed PWM controller chip 41b9c491dSGuenter Roeck * 51b9c491dSGuenter Roeck * Copyright (C) 2007 Herbert Valerio Riedel <hvr@gnu.org> 61b9c491dSGuenter Roeck * 71b9c491dSGuenter Roeck * Complete datasheet is available at GMT's website: 81b9c491dSGuenter Roeck * http://www.gmt.com.tw/product/datasheet/EDS-760A.pdf 91b9c491dSGuenter Roeck * 101b9c491dSGuenter Roeck * This program is free software; you can redistribute it and/or modify 111b9c491dSGuenter Roeck * it under the terms of the GNU General Public License as published by 121b9c491dSGuenter Roeck * the Free Software Foundation; either version 2 of the License, or 131b9c491dSGuenter Roeck * (at your option) any later version. 143bcfa9e4SHerbert Valerio Riedel */ 153bcfa9e4SHerbert Valerio Riedel 163bcfa9e4SHerbert Valerio Riedel #include <linux/module.h> 173bcfa9e4SHerbert Valerio Riedel #include <linux/init.h> 183bcfa9e4SHerbert Valerio Riedel #include <linux/slab.h> 193bcfa9e4SHerbert Valerio Riedel #include <linux/jiffies.h> 203bcfa9e4SHerbert Valerio Riedel #include <linux/i2c.h> 213bcfa9e4SHerbert Valerio Riedel #include <linux/hwmon.h> 223bcfa9e4SHerbert Valerio Riedel #include <linux/hwmon-sysfs.h> 233bcfa9e4SHerbert Valerio Riedel #include <linux/err.h> 243bcfa9e4SHerbert Valerio Riedel #include <linux/mutex.h> 253bcfa9e4SHerbert Valerio Riedel #include <linux/sysfs.h> 263bcfa9e4SHerbert Valerio Riedel 273bcfa9e4SHerbert Valerio Riedel enum g760a_regs { 283bcfa9e4SHerbert Valerio Riedel G760A_REG_SET_CNT = 0x00, 293bcfa9e4SHerbert Valerio Riedel G760A_REG_ACT_CNT = 0x01, 303bcfa9e4SHerbert Valerio Riedel G760A_REG_FAN_STA = 0x02 313bcfa9e4SHerbert Valerio Riedel }; 323bcfa9e4SHerbert Valerio Riedel 333bcfa9e4SHerbert Valerio Riedel #define G760A_REG_FAN_STA_RPM_OFF 0x1 /* +/-20% off */ 343bcfa9e4SHerbert Valerio Riedel #define G760A_REG_FAN_STA_RPM_LOW 0x2 /* below 1920rpm */ 353bcfa9e4SHerbert Valerio Riedel 363bcfa9e4SHerbert Valerio Riedel /* register data is read (and cached) at most once per second */ 373bcfa9e4SHerbert Valerio Riedel #define G760A_UPDATE_INTERVAL (HZ) 383bcfa9e4SHerbert Valerio Riedel 393bcfa9e4SHerbert Valerio Riedel struct g760a_data { 403bcfa9e4SHerbert Valerio Riedel struct i2c_client *client; 413bcfa9e4SHerbert Valerio Riedel struct mutex update_lock; 423bcfa9e4SHerbert Valerio Riedel 433bcfa9e4SHerbert Valerio Riedel /* board specific parameters */ 443bcfa9e4SHerbert Valerio Riedel u32 clk; /* default 32kHz */ 453bcfa9e4SHerbert Valerio Riedel u16 fan_div; /* default P=2 */ 463bcfa9e4SHerbert Valerio Riedel 473bcfa9e4SHerbert Valerio Riedel /* g760a register cache */ 483bcfa9e4SHerbert Valerio Riedel unsigned int valid:1; 493bcfa9e4SHerbert Valerio Riedel unsigned long last_updated; /* In jiffies */ 503bcfa9e4SHerbert Valerio Riedel 513bcfa9e4SHerbert Valerio Riedel u8 set_cnt; /* PWM (period) count number; 0xff stops fan */ 523bcfa9e4SHerbert Valerio Riedel u8 act_cnt; /* formula: cnt = (CLK * 30)/(rpm * P) */ 533bcfa9e4SHerbert Valerio Riedel u8 fan_sta; /* bit 0: set when actual fan speed more than 20% 543bcfa9e4SHerbert Valerio Riedel * outside requested fan speed 551b9c491dSGuenter Roeck * bit 1: set when fan speed below 1920 rpm 561b9c491dSGuenter Roeck */ 573bcfa9e4SHerbert Valerio Riedel }; 583bcfa9e4SHerbert Valerio Riedel 593bcfa9e4SHerbert Valerio Riedel #define G760A_DEFAULT_CLK 32768 603bcfa9e4SHerbert Valerio Riedel #define G760A_DEFAULT_FAN_DIV 2 613bcfa9e4SHerbert Valerio Riedel 623bcfa9e4SHerbert Valerio Riedel #define PWM_FROM_CNT(cnt) (0xff-(cnt)) 633bcfa9e4SHerbert Valerio Riedel #define PWM_TO_CNT(pwm) (0xff-(pwm)) 643bcfa9e4SHerbert Valerio Riedel 65ebec05bdSJean Delvare static inline unsigned int rpm_from_cnt(u8 val, u32 clk, u16 div) 663bcfa9e4SHerbert Valerio Riedel { 673bcfa9e4SHerbert Valerio Riedel return ((val == 0x00) ? 0 : ((clk*30)/(val*div))); 683bcfa9e4SHerbert Valerio Riedel } 693bcfa9e4SHerbert Valerio Riedel 703bcfa9e4SHerbert Valerio Riedel /* read/write wrappers */ 713bcfa9e4SHerbert Valerio Riedel static int g760a_read_value(struct i2c_client *client, enum g760a_regs reg) 723bcfa9e4SHerbert Valerio Riedel { 733bcfa9e4SHerbert Valerio Riedel return i2c_smbus_read_byte_data(client, reg); 743bcfa9e4SHerbert Valerio Riedel } 753bcfa9e4SHerbert Valerio Riedel 763bcfa9e4SHerbert Valerio Riedel static int g760a_write_value(struct i2c_client *client, enum g760a_regs reg, 773bcfa9e4SHerbert Valerio Riedel u16 value) 783bcfa9e4SHerbert Valerio Riedel { 793bcfa9e4SHerbert Valerio Riedel return i2c_smbus_write_byte_data(client, reg, value); 803bcfa9e4SHerbert Valerio Riedel } 813bcfa9e4SHerbert Valerio Riedel 821b9c491dSGuenter Roeck /* 833bcfa9e4SHerbert Valerio Riedel * sysfs attributes 843bcfa9e4SHerbert Valerio Riedel */ 853bcfa9e4SHerbert Valerio Riedel 863bcfa9e4SHerbert Valerio Riedel static struct g760a_data *g760a_update_client(struct device *dev) 873bcfa9e4SHerbert Valerio Riedel { 88*e47c39b3SAxel Lin struct g760a_data *data = dev_get_drvdata(dev); 89*e47c39b3SAxel Lin struct i2c_client *client = data->client; 903bcfa9e4SHerbert Valerio Riedel 913bcfa9e4SHerbert Valerio Riedel mutex_lock(&data->update_lock); 923bcfa9e4SHerbert Valerio Riedel 933bcfa9e4SHerbert Valerio Riedel if (time_after(jiffies, data->last_updated + G760A_UPDATE_INTERVAL) 943bcfa9e4SHerbert Valerio Riedel || !data->valid) { 953bcfa9e4SHerbert Valerio Riedel dev_dbg(&client->dev, "Starting g760a update\n"); 963bcfa9e4SHerbert Valerio Riedel 973bcfa9e4SHerbert Valerio Riedel data->set_cnt = g760a_read_value(client, G760A_REG_SET_CNT); 983bcfa9e4SHerbert Valerio Riedel data->act_cnt = g760a_read_value(client, G760A_REG_ACT_CNT); 993bcfa9e4SHerbert Valerio Riedel data->fan_sta = g760a_read_value(client, G760A_REG_FAN_STA); 1003bcfa9e4SHerbert Valerio Riedel 1013bcfa9e4SHerbert Valerio Riedel data->last_updated = jiffies; 1023bcfa9e4SHerbert Valerio Riedel data->valid = 1; 1033bcfa9e4SHerbert Valerio Riedel } 1043bcfa9e4SHerbert Valerio Riedel 1053bcfa9e4SHerbert Valerio Riedel mutex_unlock(&data->update_lock); 1063bcfa9e4SHerbert Valerio Riedel 1073bcfa9e4SHerbert Valerio Riedel return data; 1083bcfa9e4SHerbert Valerio Riedel } 1093bcfa9e4SHerbert Valerio Riedel 1103bcfa9e4SHerbert Valerio Riedel static ssize_t show_fan(struct device *dev, struct device_attribute *da, 1113bcfa9e4SHerbert Valerio Riedel char *buf) 1123bcfa9e4SHerbert Valerio Riedel { 1133bcfa9e4SHerbert Valerio Riedel struct g760a_data *data = g760a_update_client(dev); 1143bcfa9e4SHerbert Valerio Riedel unsigned int rpm = 0; 1153bcfa9e4SHerbert Valerio Riedel 1163bcfa9e4SHerbert Valerio Riedel mutex_lock(&data->update_lock); 1173bcfa9e4SHerbert Valerio Riedel if (!(data->fan_sta & G760A_REG_FAN_STA_RPM_LOW)) 1183bcfa9e4SHerbert Valerio Riedel rpm = rpm_from_cnt(data->act_cnt, data->clk, data->fan_div); 1193bcfa9e4SHerbert Valerio Riedel mutex_unlock(&data->update_lock); 1203bcfa9e4SHerbert Valerio Riedel 1213bcfa9e4SHerbert Valerio Riedel return sprintf(buf, "%d\n", rpm); 1223bcfa9e4SHerbert Valerio Riedel } 1233bcfa9e4SHerbert Valerio Riedel 1243bcfa9e4SHerbert Valerio Riedel static ssize_t show_fan_alarm(struct device *dev, struct device_attribute *da, 1253bcfa9e4SHerbert Valerio Riedel char *buf) 1263bcfa9e4SHerbert Valerio Riedel { 1273bcfa9e4SHerbert Valerio Riedel struct g760a_data *data = g760a_update_client(dev); 1283bcfa9e4SHerbert Valerio Riedel 1293bcfa9e4SHerbert Valerio Riedel int fan_alarm = (data->fan_sta & G760A_REG_FAN_STA_RPM_OFF) ? 1 : 0; 1303bcfa9e4SHerbert Valerio Riedel 1313bcfa9e4SHerbert Valerio Riedel return sprintf(buf, "%d\n", fan_alarm); 1323bcfa9e4SHerbert Valerio Riedel } 1333bcfa9e4SHerbert Valerio Riedel 1343bcfa9e4SHerbert Valerio Riedel static ssize_t get_pwm(struct device *dev, struct device_attribute *da, 1353bcfa9e4SHerbert Valerio Riedel char *buf) 1363bcfa9e4SHerbert Valerio Riedel { 1373bcfa9e4SHerbert Valerio Riedel struct g760a_data *data = g760a_update_client(dev); 1383bcfa9e4SHerbert Valerio Riedel 1393bcfa9e4SHerbert Valerio Riedel return sprintf(buf, "%d\n", PWM_FROM_CNT(data->set_cnt)); 1403bcfa9e4SHerbert Valerio Riedel } 1413bcfa9e4SHerbert Valerio Riedel 1423bcfa9e4SHerbert Valerio Riedel static ssize_t set_pwm(struct device *dev, struct device_attribute *da, 1433bcfa9e4SHerbert Valerio Riedel const char *buf, size_t count) 1443bcfa9e4SHerbert Valerio Riedel { 1453bcfa9e4SHerbert Valerio Riedel struct g760a_data *data = g760a_update_client(dev); 146*e47c39b3SAxel Lin struct i2c_client *client = data->client; 1473bcfa9e4SHerbert Valerio Riedel unsigned long val; 1483bcfa9e4SHerbert Valerio Riedel 149179c4fdbSFrans Meulenbroeks if (kstrtoul(buf, 10, &val)) 1503bcfa9e4SHerbert Valerio Riedel return -EINVAL; 1513bcfa9e4SHerbert Valerio Riedel 1523bcfa9e4SHerbert Valerio Riedel mutex_lock(&data->update_lock); 1532a844c14SGuenter Roeck data->set_cnt = PWM_TO_CNT(clamp_val(val, 0, 255)); 1543bcfa9e4SHerbert Valerio Riedel g760a_write_value(client, G760A_REG_SET_CNT, data->set_cnt); 1553bcfa9e4SHerbert Valerio Riedel mutex_unlock(&data->update_lock); 1563bcfa9e4SHerbert Valerio Riedel 1573bcfa9e4SHerbert Valerio Riedel return count; 1583bcfa9e4SHerbert Valerio Riedel } 1593bcfa9e4SHerbert Valerio Riedel 1603bcfa9e4SHerbert Valerio Riedel static DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, get_pwm, set_pwm); 1613bcfa9e4SHerbert Valerio Riedel static DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL); 1623bcfa9e4SHerbert Valerio Riedel static DEVICE_ATTR(fan1_alarm, S_IRUGO, show_fan_alarm, NULL); 1633bcfa9e4SHerbert Valerio Riedel 164*e47c39b3SAxel Lin static struct attribute *g760a_attrs[] = { 1653bcfa9e4SHerbert Valerio Riedel &dev_attr_pwm1.attr, 1663bcfa9e4SHerbert Valerio Riedel &dev_attr_fan1_input.attr, 1673bcfa9e4SHerbert Valerio Riedel &dev_attr_fan1_alarm.attr, 1683bcfa9e4SHerbert Valerio Riedel NULL 1693bcfa9e4SHerbert Valerio Riedel }; 1703bcfa9e4SHerbert Valerio Riedel 171*e47c39b3SAxel Lin ATTRIBUTE_GROUPS(g760a); 1723bcfa9e4SHerbert Valerio Riedel 1731b9c491dSGuenter Roeck /* 1743bcfa9e4SHerbert Valerio Riedel * new-style driver model code 1753bcfa9e4SHerbert Valerio Riedel */ 1763bcfa9e4SHerbert Valerio Riedel 1773bcfa9e4SHerbert Valerio Riedel static int g760a_probe(struct i2c_client *client, 1783bcfa9e4SHerbert Valerio Riedel const struct i2c_device_id *id) 1793bcfa9e4SHerbert Valerio Riedel { 180*e47c39b3SAxel Lin struct device *dev = &client->dev; 1813bcfa9e4SHerbert Valerio Riedel struct g760a_data *data; 182*e47c39b3SAxel Lin struct device *hwmon_dev; 1833bcfa9e4SHerbert Valerio Riedel 184*e47c39b3SAxel Lin if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) 1853bcfa9e4SHerbert Valerio Riedel return -EIO; 1863bcfa9e4SHerbert Valerio Riedel 187*e47c39b3SAxel Lin data = devm_kzalloc(dev, sizeof(struct g760a_data), GFP_KERNEL); 1883bcfa9e4SHerbert Valerio Riedel if (!data) 1893bcfa9e4SHerbert Valerio Riedel return -ENOMEM; 1903bcfa9e4SHerbert Valerio Riedel 1913bcfa9e4SHerbert Valerio Riedel data->client = client; 1923bcfa9e4SHerbert Valerio Riedel mutex_init(&data->update_lock); 1933bcfa9e4SHerbert Valerio Riedel 1943bcfa9e4SHerbert Valerio Riedel /* setup default configuration for now */ 1953bcfa9e4SHerbert Valerio Riedel data->fan_div = G760A_DEFAULT_FAN_DIV; 1963bcfa9e4SHerbert Valerio Riedel data->clk = G760A_DEFAULT_CLK; 1973bcfa9e4SHerbert Valerio Riedel 198*e47c39b3SAxel Lin hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, 199*e47c39b3SAxel Lin data, 200*e47c39b3SAxel Lin g760a_groups); 201*e47c39b3SAxel Lin return PTR_ERR_OR_ZERO(hwmon_dev); 2023bcfa9e4SHerbert Valerio Riedel } 2033bcfa9e4SHerbert Valerio Riedel 20415ac4ddbSAxel Lin static const struct i2c_device_id g760a_id[] = { 20515ac4ddbSAxel Lin { "g760a", 0 }, 20615ac4ddbSAxel Lin { } 20715ac4ddbSAxel Lin }; 20815ac4ddbSAxel Lin MODULE_DEVICE_TABLE(i2c, g760a_id); 20915ac4ddbSAxel Lin 21015ac4ddbSAxel Lin static struct i2c_driver g760a_driver = { 21115ac4ddbSAxel Lin .driver = { 21215ac4ddbSAxel Lin .name = "g760a", 21315ac4ddbSAxel Lin }, 21415ac4ddbSAxel Lin .probe = g760a_probe, 21515ac4ddbSAxel Lin .id_table = g760a_id, 21615ac4ddbSAxel Lin }; 21715ac4ddbSAxel Lin 218f0967eeaSAxel Lin module_i2c_driver(g760a_driver); 2193bcfa9e4SHerbert Valerio Riedel 2203bcfa9e4SHerbert Valerio Riedel MODULE_AUTHOR("Herbert Valerio Riedel <hvr@gnu.org>"); 2213bcfa9e4SHerbert Valerio Riedel MODULE_DESCRIPTION("GMT G760A driver"); 2223bcfa9e4SHerbert Valerio Riedel MODULE_LICENSE("GPL"); 223