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 static const struct i2c_device_id g760a_id[] = { 283bcfa9e4SHerbert Valerio Riedel { "g760a", 0 }, 293bcfa9e4SHerbert Valerio Riedel { } 303bcfa9e4SHerbert Valerio Riedel }; 313bcfa9e4SHerbert Valerio Riedel MODULE_DEVICE_TABLE(i2c, g760a_id); 323bcfa9e4SHerbert Valerio Riedel 333bcfa9e4SHerbert Valerio Riedel enum g760a_regs { 343bcfa9e4SHerbert Valerio Riedel G760A_REG_SET_CNT = 0x00, 353bcfa9e4SHerbert Valerio Riedel G760A_REG_ACT_CNT = 0x01, 363bcfa9e4SHerbert Valerio Riedel G760A_REG_FAN_STA = 0x02 373bcfa9e4SHerbert Valerio Riedel }; 383bcfa9e4SHerbert Valerio Riedel 393bcfa9e4SHerbert Valerio Riedel #define G760A_REG_FAN_STA_RPM_OFF 0x1 /* +/-20% off */ 403bcfa9e4SHerbert Valerio Riedel #define G760A_REG_FAN_STA_RPM_LOW 0x2 /* below 1920rpm */ 413bcfa9e4SHerbert Valerio Riedel 423bcfa9e4SHerbert Valerio Riedel /* register data is read (and cached) at most once per second */ 433bcfa9e4SHerbert Valerio Riedel #define G760A_UPDATE_INTERVAL (HZ) 443bcfa9e4SHerbert Valerio Riedel 453bcfa9e4SHerbert Valerio Riedel struct g760a_data { 463bcfa9e4SHerbert Valerio Riedel struct i2c_client *client; 473bcfa9e4SHerbert Valerio Riedel struct device *hwmon_dev; 483bcfa9e4SHerbert Valerio Riedel struct mutex update_lock; 493bcfa9e4SHerbert Valerio Riedel 503bcfa9e4SHerbert Valerio Riedel /* board specific parameters */ 513bcfa9e4SHerbert Valerio Riedel u32 clk; /* default 32kHz */ 523bcfa9e4SHerbert Valerio Riedel u16 fan_div; /* default P=2 */ 533bcfa9e4SHerbert Valerio Riedel 543bcfa9e4SHerbert Valerio Riedel /* g760a register cache */ 553bcfa9e4SHerbert Valerio Riedel unsigned int valid:1; 563bcfa9e4SHerbert Valerio Riedel unsigned long last_updated; /* In jiffies */ 573bcfa9e4SHerbert Valerio Riedel 583bcfa9e4SHerbert Valerio Riedel u8 set_cnt; /* PWM (period) count number; 0xff stops fan */ 593bcfa9e4SHerbert Valerio Riedel u8 act_cnt; /* formula: cnt = (CLK * 30)/(rpm * P) */ 603bcfa9e4SHerbert Valerio Riedel u8 fan_sta; /* bit 0: set when actual fan speed more than 20% 613bcfa9e4SHerbert Valerio Riedel * outside requested fan speed 621b9c491dSGuenter Roeck * bit 1: set when fan speed below 1920 rpm 631b9c491dSGuenter Roeck */ 643bcfa9e4SHerbert Valerio Riedel }; 653bcfa9e4SHerbert Valerio Riedel 663bcfa9e4SHerbert Valerio Riedel #define G760A_DEFAULT_CLK 32768 673bcfa9e4SHerbert Valerio Riedel #define G760A_DEFAULT_FAN_DIV 2 683bcfa9e4SHerbert Valerio Riedel 693bcfa9e4SHerbert Valerio Riedel #define PWM_FROM_CNT(cnt) (0xff-(cnt)) 703bcfa9e4SHerbert Valerio Riedel #define PWM_TO_CNT(pwm) (0xff-(pwm)) 713bcfa9e4SHerbert Valerio Riedel 72ebec05bdSJean Delvare static inline unsigned int rpm_from_cnt(u8 val, u32 clk, u16 div) 733bcfa9e4SHerbert Valerio Riedel { 743bcfa9e4SHerbert Valerio Riedel return ((val == 0x00) ? 0 : ((clk*30)/(val*div))); 753bcfa9e4SHerbert Valerio Riedel } 763bcfa9e4SHerbert Valerio Riedel 773bcfa9e4SHerbert Valerio Riedel /* new-style driver model */ 783bcfa9e4SHerbert Valerio Riedel static int g760a_probe(struct i2c_client *client, 793bcfa9e4SHerbert Valerio Riedel const struct i2c_device_id *id); 803bcfa9e4SHerbert Valerio Riedel static int g760a_remove(struct i2c_client *client); 813bcfa9e4SHerbert Valerio Riedel 823bcfa9e4SHerbert Valerio Riedel static struct i2c_driver g760a_driver = { 833bcfa9e4SHerbert Valerio Riedel .driver = { 843bcfa9e4SHerbert Valerio Riedel .name = "g760a", 853bcfa9e4SHerbert Valerio Riedel }, 863bcfa9e4SHerbert Valerio Riedel .probe = g760a_probe, 873bcfa9e4SHerbert Valerio Riedel .remove = g760a_remove, 883bcfa9e4SHerbert Valerio Riedel .id_table = g760a_id, 893bcfa9e4SHerbert Valerio Riedel }; 903bcfa9e4SHerbert Valerio Riedel 913bcfa9e4SHerbert Valerio Riedel /* read/write wrappers */ 923bcfa9e4SHerbert Valerio Riedel static int g760a_read_value(struct i2c_client *client, enum g760a_regs reg) 933bcfa9e4SHerbert Valerio Riedel { 943bcfa9e4SHerbert Valerio Riedel return i2c_smbus_read_byte_data(client, reg); 953bcfa9e4SHerbert Valerio Riedel } 963bcfa9e4SHerbert Valerio Riedel 973bcfa9e4SHerbert Valerio Riedel static int g760a_write_value(struct i2c_client *client, enum g760a_regs reg, 983bcfa9e4SHerbert Valerio Riedel u16 value) 993bcfa9e4SHerbert Valerio Riedel { 1003bcfa9e4SHerbert Valerio Riedel return i2c_smbus_write_byte_data(client, reg, value); 1013bcfa9e4SHerbert Valerio Riedel } 1023bcfa9e4SHerbert Valerio Riedel 1031b9c491dSGuenter Roeck /* 1043bcfa9e4SHerbert Valerio Riedel * sysfs attributes 1053bcfa9e4SHerbert Valerio Riedel */ 1063bcfa9e4SHerbert Valerio Riedel 1073bcfa9e4SHerbert Valerio Riedel static struct g760a_data *g760a_update_client(struct device *dev) 1083bcfa9e4SHerbert Valerio Riedel { 1093bcfa9e4SHerbert Valerio Riedel struct i2c_client *client = to_i2c_client(dev); 1103bcfa9e4SHerbert Valerio Riedel struct g760a_data *data = i2c_get_clientdata(client); 1113bcfa9e4SHerbert Valerio Riedel 1123bcfa9e4SHerbert Valerio Riedel mutex_lock(&data->update_lock); 1133bcfa9e4SHerbert Valerio Riedel 1143bcfa9e4SHerbert Valerio Riedel if (time_after(jiffies, data->last_updated + G760A_UPDATE_INTERVAL) 1153bcfa9e4SHerbert Valerio Riedel || !data->valid) { 1163bcfa9e4SHerbert Valerio Riedel dev_dbg(&client->dev, "Starting g760a update\n"); 1173bcfa9e4SHerbert Valerio Riedel 1183bcfa9e4SHerbert Valerio Riedel data->set_cnt = g760a_read_value(client, G760A_REG_SET_CNT); 1193bcfa9e4SHerbert Valerio Riedel data->act_cnt = g760a_read_value(client, G760A_REG_ACT_CNT); 1203bcfa9e4SHerbert Valerio Riedel data->fan_sta = g760a_read_value(client, G760A_REG_FAN_STA); 1213bcfa9e4SHerbert Valerio Riedel 1223bcfa9e4SHerbert Valerio Riedel data->last_updated = jiffies; 1233bcfa9e4SHerbert Valerio Riedel data->valid = 1; 1243bcfa9e4SHerbert Valerio Riedel } 1253bcfa9e4SHerbert Valerio Riedel 1263bcfa9e4SHerbert Valerio Riedel mutex_unlock(&data->update_lock); 1273bcfa9e4SHerbert Valerio Riedel 1283bcfa9e4SHerbert Valerio Riedel return data; 1293bcfa9e4SHerbert Valerio Riedel } 1303bcfa9e4SHerbert Valerio Riedel 1313bcfa9e4SHerbert Valerio Riedel static ssize_t show_fan(struct device *dev, struct device_attribute *da, 1323bcfa9e4SHerbert Valerio Riedel char *buf) 1333bcfa9e4SHerbert Valerio Riedel { 1343bcfa9e4SHerbert Valerio Riedel struct g760a_data *data = g760a_update_client(dev); 1353bcfa9e4SHerbert Valerio Riedel unsigned int rpm = 0; 1363bcfa9e4SHerbert Valerio Riedel 1373bcfa9e4SHerbert Valerio Riedel mutex_lock(&data->update_lock); 1383bcfa9e4SHerbert Valerio Riedel if (!(data->fan_sta & G760A_REG_FAN_STA_RPM_LOW)) 1393bcfa9e4SHerbert Valerio Riedel rpm = rpm_from_cnt(data->act_cnt, data->clk, data->fan_div); 1403bcfa9e4SHerbert Valerio Riedel mutex_unlock(&data->update_lock); 1413bcfa9e4SHerbert Valerio Riedel 1423bcfa9e4SHerbert Valerio Riedel return sprintf(buf, "%d\n", rpm); 1433bcfa9e4SHerbert Valerio Riedel } 1443bcfa9e4SHerbert Valerio Riedel 1453bcfa9e4SHerbert Valerio Riedel static ssize_t show_fan_alarm(struct device *dev, struct device_attribute *da, 1463bcfa9e4SHerbert Valerio Riedel char *buf) 1473bcfa9e4SHerbert Valerio Riedel { 1483bcfa9e4SHerbert Valerio Riedel struct g760a_data *data = g760a_update_client(dev); 1493bcfa9e4SHerbert Valerio Riedel 1503bcfa9e4SHerbert Valerio Riedel int fan_alarm = (data->fan_sta & G760A_REG_FAN_STA_RPM_OFF) ? 1 : 0; 1513bcfa9e4SHerbert Valerio Riedel 1523bcfa9e4SHerbert Valerio Riedel return sprintf(buf, "%d\n", fan_alarm); 1533bcfa9e4SHerbert Valerio Riedel } 1543bcfa9e4SHerbert Valerio Riedel 1553bcfa9e4SHerbert Valerio Riedel static ssize_t get_pwm(struct device *dev, struct device_attribute *da, 1563bcfa9e4SHerbert Valerio Riedel char *buf) 1573bcfa9e4SHerbert Valerio Riedel { 1583bcfa9e4SHerbert Valerio Riedel struct g760a_data *data = g760a_update_client(dev); 1593bcfa9e4SHerbert Valerio Riedel 1603bcfa9e4SHerbert Valerio Riedel return sprintf(buf, "%d\n", PWM_FROM_CNT(data->set_cnt)); 1613bcfa9e4SHerbert Valerio Riedel } 1623bcfa9e4SHerbert Valerio Riedel 1633bcfa9e4SHerbert Valerio Riedel static ssize_t set_pwm(struct device *dev, struct device_attribute *da, 1643bcfa9e4SHerbert Valerio Riedel const char *buf, size_t count) 1653bcfa9e4SHerbert Valerio Riedel { 1663bcfa9e4SHerbert Valerio Riedel struct i2c_client *client = to_i2c_client(dev); 1673bcfa9e4SHerbert Valerio Riedel struct g760a_data *data = g760a_update_client(dev); 1683bcfa9e4SHerbert Valerio Riedel unsigned long val; 1693bcfa9e4SHerbert Valerio Riedel 170179c4fdbSFrans Meulenbroeks if (kstrtoul(buf, 10, &val)) 1713bcfa9e4SHerbert Valerio Riedel return -EINVAL; 1723bcfa9e4SHerbert Valerio Riedel 1733bcfa9e4SHerbert Valerio Riedel mutex_lock(&data->update_lock); 174*2a844c14SGuenter Roeck data->set_cnt = PWM_TO_CNT(clamp_val(val, 0, 255)); 1753bcfa9e4SHerbert Valerio Riedel g760a_write_value(client, G760A_REG_SET_CNT, data->set_cnt); 1763bcfa9e4SHerbert Valerio Riedel mutex_unlock(&data->update_lock); 1773bcfa9e4SHerbert Valerio Riedel 1783bcfa9e4SHerbert Valerio Riedel return count; 1793bcfa9e4SHerbert Valerio Riedel } 1803bcfa9e4SHerbert Valerio Riedel 1813bcfa9e4SHerbert Valerio Riedel static DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, get_pwm, set_pwm); 1823bcfa9e4SHerbert Valerio Riedel static DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL); 1833bcfa9e4SHerbert Valerio Riedel static DEVICE_ATTR(fan1_alarm, S_IRUGO, show_fan_alarm, NULL); 1843bcfa9e4SHerbert Valerio Riedel 1853bcfa9e4SHerbert Valerio Riedel static struct attribute *g760a_attributes[] = { 1863bcfa9e4SHerbert Valerio Riedel &dev_attr_pwm1.attr, 1873bcfa9e4SHerbert Valerio Riedel &dev_attr_fan1_input.attr, 1883bcfa9e4SHerbert Valerio Riedel &dev_attr_fan1_alarm.attr, 1893bcfa9e4SHerbert Valerio Riedel NULL 1903bcfa9e4SHerbert Valerio Riedel }; 1913bcfa9e4SHerbert Valerio Riedel 1923bcfa9e4SHerbert Valerio Riedel static const struct attribute_group g760a_group = { 1933bcfa9e4SHerbert Valerio Riedel .attrs = g760a_attributes, 1943bcfa9e4SHerbert Valerio Riedel }; 1953bcfa9e4SHerbert Valerio Riedel 1961b9c491dSGuenter Roeck /* 1973bcfa9e4SHerbert Valerio Riedel * new-style driver model code 1983bcfa9e4SHerbert Valerio Riedel */ 1993bcfa9e4SHerbert Valerio Riedel 2003bcfa9e4SHerbert Valerio Riedel static int g760a_probe(struct i2c_client *client, 2013bcfa9e4SHerbert Valerio Riedel const struct i2c_device_id *id) 2023bcfa9e4SHerbert Valerio Riedel { 2033bcfa9e4SHerbert Valerio Riedel struct g760a_data *data; 2043bcfa9e4SHerbert Valerio Riedel int err; 2053bcfa9e4SHerbert Valerio Riedel 2063bcfa9e4SHerbert Valerio Riedel if (!i2c_check_functionality(client->adapter, 2073bcfa9e4SHerbert Valerio Riedel I2C_FUNC_SMBUS_BYTE_DATA)) 2083bcfa9e4SHerbert Valerio Riedel return -EIO; 2093bcfa9e4SHerbert Valerio Riedel 210a81b0f73SGuenter Roeck data = devm_kzalloc(&client->dev, sizeof(struct g760a_data), 211a81b0f73SGuenter Roeck GFP_KERNEL); 2123bcfa9e4SHerbert Valerio Riedel if (!data) 2133bcfa9e4SHerbert Valerio Riedel return -ENOMEM; 2143bcfa9e4SHerbert Valerio Riedel 2153bcfa9e4SHerbert Valerio Riedel i2c_set_clientdata(client, data); 2163bcfa9e4SHerbert Valerio Riedel 2173bcfa9e4SHerbert Valerio Riedel data->client = client; 2183bcfa9e4SHerbert Valerio Riedel mutex_init(&data->update_lock); 2193bcfa9e4SHerbert Valerio Riedel 2203bcfa9e4SHerbert Valerio Riedel /* setup default configuration for now */ 2213bcfa9e4SHerbert Valerio Riedel data->fan_div = G760A_DEFAULT_FAN_DIV; 2223bcfa9e4SHerbert Valerio Riedel data->clk = G760A_DEFAULT_CLK; 2233bcfa9e4SHerbert Valerio Riedel 2243bcfa9e4SHerbert Valerio Riedel /* Register sysfs hooks */ 2253bcfa9e4SHerbert Valerio Riedel err = sysfs_create_group(&client->dev.kobj, &g760a_group); 2263bcfa9e4SHerbert Valerio Riedel if (err) 227a81b0f73SGuenter Roeck return err; 2283bcfa9e4SHerbert Valerio Riedel 2293bcfa9e4SHerbert Valerio Riedel data->hwmon_dev = hwmon_device_register(&client->dev); 2303bcfa9e4SHerbert Valerio Riedel if (IS_ERR(data->hwmon_dev)) { 2313bcfa9e4SHerbert Valerio Riedel err = PTR_ERR(data->hwmon_dev); 2323bcfa9e4SHerbert Valerio Riedel goto error_hwmon_device_register; 2333bcfa9e4SHerbert Valerio Riedel } 2343bcfa9e4SHerbert Valerio Riedel 2353bcfa9e4SHerbert Valerio Riedel return 0; 2363bcfa9e4SHerbert Valerio Riedel 2373bcfa9e4SHerbert Valerio Riedel error_hwmon_device_register: 2383bcfa9e4SHerbert Valerio Riedel sysfs_remove_group(&client->dev.kobj, &g760a_group); 2393bcfa9e4SHerbert Valerio Riedel return err; 2403bcfa9e4SHerbert Valerio Riedel } 2413bcfa9e4SHerbert Valerio Riedel 2423bcfa9e4SHerbert Valerio Riedel static int g760a_remove(struct i2c_client *client) 2433bcfa9e4SHerbert Valerio Riedel { 2443bcfa9e4SHerbert Valerio Riedel struct g760a_data *data = i2c_get_clientdata(client); 2453bcfa9e4SHerbert Valerio Riedel hwmon_device_unregister(data->hwmon_dev); 2463bcfa9e4SHerbert Valerio Riedel sysfs_remove_group(&client->dev.kobj, &g760a_group); 2473bcfa9e4SHerbert Valerio Riedel return 0; 2483bcfa9e4SHerbert Valerio Riedel } 2493bcfa9e4SHerbert Valerio Riedel 250f0967eeaSAxel Lin module_i2c_driver(g760a_driver); 2513bcfa9e4SHerbert Valerio Riedel 2523bcfa9e4SHerbert Valerio Riedel MODULE_AUTHOR("Herbert Valerio Riedel <hvr@gnu.org>"); 2533bcfa9e4SHerbert Valerio Riedel MODULE_DESCRIPTION("GMT G760A driver"); 2543bcfa9e4SHerbert Valerio Riedel MODULE_LICENSE("GPL"); 255