18d5d45fbSJean Delvare /* 2f24d548bSGuenter Roeck * atxp1.c - kernel module for setting CPU VID and general purpose 3f24d548bSGuenter Roeck * I/Os using the Attansic ATXP1 chip. 4f24d548bSGuenter Roeck * 5f24d548bSGuenter Roeck * This program is free software; you can redistribute it and/or modify 6f24d548bSGuenter Roeck * it under the terms of the GNU General Public License as published by 7f24d548bSGuenter Roeck * the Free Software Foundation; either version 2 of the License, or 8f24d548bSGuenter Roeck * (at your option) any later version. 9f24d548bSGuenter Roeck * 10f24d548bSGuenter Roeck * This program is distributed in the hope that it will be useful, 11f24d548bSGuenter Roeck * but WITHOUT ANY WARRANTY; without even the implied warranty of 12f24d548bSGuenter Roeck * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13f24d548bSGuenter Roeck * GNU General Public License for more details. 14f24d548bSGuenter Roeck * 15f24d548bSGuenter Roeck * You should have received a copy of the GNU General Public License 16f24d548bSGuenter Roeck * along with this program; if not, write to the Free Software 17f24d548bSGuenter Roeck * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 18f24d548bSGuenter Roeck * 198d5d45fbSJean Delvare */ 208d5d45fbSJean Delvare 218d5d45fbSJean Delvare #include <linux/kernel.h> 228d5d45fbSJean Delvare #include <linux/init.h> 238d5d45fbSJean Delvare #include <linux/module.h> 240cacdf29SJean Delvare #include <linux/jiffies.h> 258d5d45fbSJean Delvare #include <linux/i2c.h> 26943b0830SMark M. Hoffman #include <linux/hwmon.h> 27303760b4SJean Delvare #include <linux/hwmon-vid.h> 28943b0830SMark M. Hoffman #include <linux/err.h> 299a61bf63SIngo Molnar #include <linux/mutex.h> 30a5ebe668SJean Delvare #include <linux/sysfs.h> 315a0e3ad6STejun Heo #include <linux/slab.h> 328d5d45fbSJean Delvare 338d5d45fbSJean Delvare MODULE_LICENSE("GPL"); 348d5d45fbSJean Delvare MODULE_DESCRIPTION("System voltages control via Attansic ATXP1"); 3513b3c3faSJean Delvare MODULE_VERSION("0.6.3"); 368d5d45fbSJean Delvare MODULE_AUTHOR("Sebastian Witt <se.witt@gmx.net>"); 378d5d45fbSJean Delvare 388d5d45fbSJean Delvare #define ATXP1_VID 0x00 398d5d45fbSJean Delvare #define ATXP1_CVID 0x01 408d5d45fbSJean Delvare #define ATXP1_GPIO1 0x06 418d5d45fbSJean Delvare #define ATXP1_GPIO2 0x0a 428d5d45fbSJean Delvare #define ATXP1_VIDENA 0x20 438d5d45fbSJean Delvare #define ATXP1_VIDMASK 0x1f 448d5d45fbSJean Delvare #define ATXP1_GPIO1MASK 0x0f 458d5d45fbSJean Delvare 4625e9c86dSMark M. Hoffman static const unsigned short normal_i2c[] = { 0x37, 0x4e, I2C_CLIENT_END }; 478d5d45fbSJean Delvare 4871163c7cSJean Delvare static int atxp1_probe(struct i2c_client *client, 4971163c7cSJean Delvare const struct i2c_device_id *id); 5071163c7cSJean Delvare static int atxp1_remove(struct i2c_client *client); 518d5d45fbSJean Delvare static struct atxp1_data *atxp1_update_device(struct device *dev); 52310ec792SJean Delvare static int atxp1_detect(struct i2c_client *client, struct i2c_board_info *info); 5371163c7cSJean Delvare 5471163c7cSJean Delvare static const struct i2c_device_id atxp1_id[] = { 551f86df49SJean Delvare { "atxp1", 0 }, 5671163c7cSJean Delvare { } 5771163c7cSJean Delvare }; 5871163c7cSJean Delvare MODULE_DEVICE_TABLE(i2c, atxp1_id); 598d5d45fbSJean Delvare 608d5d45fbSJean Delvare static struct i2c_driver atxp1_driver = { 6171163c7cSJean Delvare .class = I2C_CLASS_HWMON, 62cdaf7934SLaurent Riffard .driver = { 638d5d45fbSJean Delvare .name = "atxp1", 64cdaf7934SLaurent Riffard }, 6571163c7cSJean Delvare .probe = atxp1_probe, 6671163c7cSJean Delvare .remove = atxp1_remove, 6771163c7cSJean Delvare .id_table = atxp1_id, 6871163c7cSJean Delvare .detect = atxp1_detect, 69c3813d6aSJean Delvare .address_list = normal_i2c, 708d5d45fbSJean Delvare }; 718d5d45fbSJean Delvare 728d5d45fbSJean Delvare struct atxp1_data { 731beeffe4STony Jones struct device *hwmon_dev; 749a61bf63SIngo Molnar struct mutex update_lock; 758d5d45fbSJean Delvare unsigned long last_updated; 768d5d45fbSJean Delvare u8 valid; 778d5d45fbSJean Delvare struct { 788d5d45fbSJean Delvare u8 vid; /* VID output register */ 798d5d45fbSJean Delvare u8 cpu_vid; /* VID input from CPU */ 808d5d45fbSJean Delvare u8 gpio1; /* General purpose I/O register 1 */ 818d5d45fbSJean Delvare u8 gpio2; /* General purpose I/O register 2 */ 828d5d45fbSJean Delvare } reg; 838d5d45fbSJean Delvare u8 vrm; /* Detected CPU VRM */ 848d5d45fbSJean Delvare }; 858d5d45fbSJean Delvare 868d5d45fbSJean Delvare static struct atxp1_data *atxp1_update_device(struct device *dev) 878d5d45fbSJean Delvare { 888d5d45fbSJean Delvare struct i2c_client *client; 898d5d45fbSJean Delvare struct atxp1_data *data; 908d5d45fbSJean Delvare 918d5d45fbSJean Delvare client = to_i2c_client(dev); 928d5d45fbSJean Delvare data = i2c_get_clientdata(client); 938d5d45fbSJean Delvare 949a61bf63SIngo Molnar mutex_lock(&data->update_lock); 958d5d45fbSJean Delvare 960cacdf29SJean Delvare if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { 978d5d45fbSJean Delvare 988d5d45fbSJean Delvare /* Update local register data */ 998d5d45fbSJean Delvare data->reg.vid = i2c_smbus_read_byte_data(client, ATXP1_VID); 100f24d548bSGuenter Roeck data->reg.cpu_vid = i2c_smbus_read_byte_data(client, 101f24d548bSGuenter Roeck ATXP1_CVID); 1028d5d45fbSJean Delvare data->reg.gpio1 = i2c_smbus_read_byte_data(client, ATXP1_GPIO1); 1038d5d45fbSJean Delvare data->reg.gpio2 = i2c_smbus_read_byte_data(client, ATXP1_GPIO2); 1048d5d45fbSJean Delvare 1058d5d45fbSJean Delvare data->valid = 1; 1068d5d45fbSJean Delvare } 1078d5d45fbSJean Delvare 1089a61bf63SIngo Molnar mutex_unlock(&data->update_lock); 1098d5d45fbSJean Delvare 1107fe83ad8SFrans Meulenbroeks return data; 1118d5d45fbSJean Delvare } 1128d5d45fbSJean Delvare 1138d5d45fbSJean Delvare /* sys file functions for cpu0_vid */ 114f24d548bSGuenter Roeck static ssize_t atxp1_showvcore(struct device *dev, 115f24d548bSGuenter Roeck struct device_attribute *attr, char *buf) 1168d5d45fbSJean Delvare { 1178d5d45fbSJean Delvare int size; 1188d5d45fbSJean Delvare struct atxp1_data *data; 1198d5d45fbSJean Delvare 1208d5d45fbSJean Delvare data = atxp1_update_device(dev); 1218d5d45fbSJean Delvare 122f24d548bSGuenter Roeck size = sprintf(buf, "%d\n", vid_from_reg(data->reg.vid & ATXP1_VIDMASK, 123f24d548bSGuenter Roeck data->vrm)); 1248d5d45fbSJean Delvare 1258d5d45fbSJean Delvare return size; 1268d5d45fbSJean Delvare } 1278d5d45fbSJean Delvare 128f24d548bSGuenter Roeck static ssize_t atxp1_storevcore(struct device *dev, 129f24d548bSGuenter Roeck struct device_attribute *attr, 130f24d548bSGuenter Roeck const char *buf, size_t count) 1318d5d45fbSJean Delvare { 1328d5d45fbSJean Delvare struct atxp1_data *data; 1338d5d45fbSJean Delvare struct i2c_client *client; 134c41bdb52SAlexey Dobriyan int vid, cvid; 135f24d548bSGuenter Roeck unsigned long vcore; 136f24d548bSGuenter Roeck int err; 1378d5d45fbSJean Delvare 1388d5d45fbSJean Delvare client = to_i2c_client(dev); 1398d5d45fbSJean Delvare data = atxp1_update_device(dev); 1408d5d45fbSJean Delvare 141f24d548bSGuenter Roeck err = kstrtoul(buf, 10, &vcore); 142f24d548bSGuenter Roeck if (err) 143f24d548bSGuenter Roeck return err; 144f24d548bSGuenter Roeck 1458d5d45fbSJean Delvare vcore /= 25; 1468d5d45fbSJean Delvare vcore *= 25; 1478d5d45fbSJean Delvare 1488d5d45fbSJean Delvare /* Calculate VID */ 1498d5d45fbSJean Delvare vid = vid_to_reg(vcore, data->vrm); 1508d5d45fbSJean Delvare 1518d5d45fbSJean Delvare if (vid < 0) { 1528d5d45fbSJean Delvare dev_err(dev, "VID calculation failed.\n"); 1538d5d45fbSJean Delvare return -1; 1548d5d45fbSJean Delvare } 1558d5d45fbSJean Delvare 156f24d548bSGuenter Roeck /* 157f24d548bSGuenter Roeck * If output enabled, use control register value. 158f24d548bSGuenter Roeck * Otherwise original CPU VID 159f24d548bSGuenter Roeck */ 1608d5d45fbSJean Delvare if (data->reg.vid & ATXP1_VIDENA) 1618d5d45fbSJean Delvare cvid = data->reg.vid & ATXP1_VIDMASK; 1628d5d45fbSJean Delvare else 1638d5d45fbSJean Delvare cvid = data->reg.cpu_vid; 1648d5d45fbSJean Delvare 1658d5d45fbSJean Delvare /* Nothing changed, aborting */ 1668d5d45fbSJean Delvare if (vid == cvid) 1678d5d45fbSJean Delvare return count; 1688d5d45fbSJean Delvare 169f24d548bSGuenter Roeck dev_dbg(dev, "Setting VCore to %d mV (0x%02x)\n", (int)vcore, vid); 1708d5d45fbSJean Delvare 1718d5d45fbSJean Delvare /* Write every 25 mV step to increase stability */ 1728d5d45fbSJean Delvare if (cvid > vid) { 173f24d548bSGuenter Roeck for (; cvid >= vid; cvid--) 174f24d548bSGuenter Roeck i2c_smbus_write_byte_data(client, 175f24d548bSGuenter Roeck ATXP1_VID, cvid | ATXP1_VIDENA); 176f24d548bSGuenter Roeck } else { 177f24d548bSGuenter Roeck for (; cvid <= vid; cvid++) 178f24d548bSGuenter Roeck i2c_smbus_write_byte_data(client, 179f24d548bSGuenter Roeck ATXP1_VID, cvid | ATXP1_VIDENA); 1808d5d45fbSJean Delvare } 1818d5d45fbSJean Delvare 1828d5d45fbSJean Delvare data->valid = 0; 1838d5d45fbSJean Delvare 1848d5d45fbSJean Delvare return count; 1858d5d45fbSJean Delvare } 1868d5d45fbSJean Delvare 187f24d548bSGuenter Roeck /* 188f24d548bSGuenter Roeck * CPU core reference voltage 189f24d548bSGuenter Roeck * unit: millivolt 1908d5d45fbSJean Delvare */ 191f24d548bSGuenter Roeck static DEVICE_ATTR(cpu0_vid, S_IRUGO | S_IWUSR, atxp1_showvcore, 192f24d548bSGuenter Roeck atxp1_storevcore); 1938d5d45fbSJean Delvare 1948d5d45fbSJean Delvare /* sys file functions for GPIO1 */ 195f24d548bSGuenter Roeck static ssize_t atxp1_showgpio1(struct device *dev, 196f24d548bSGuenter Roeck struct device_attribute *attr, char *buf) 1978d5d45fbSJean Delvare { 1988d5d45fbSJean Delvare int size; 1998d5d45fbSJean Delvare struct atxp1_data *data; 2008d5d45fbSJean Delvare 2018d5d45fbSJean Delvare data = atxp1_update_device(dev); 2028d5d45fbSJean Delvare 2038d5d45fbSJean Delvare size = sprintf(buf, "0x%02x\n", data->reg.gpio1 & ATXP1_GPIO1MASK); 2048d5d45fbSJean Delvare 2058d5d45fbSJean Delvare return size; 2068d5d45fbSJean Delvare } 2078d5d45fbSJean Delvare 208f24d548bSGuenter Roeck static ssize_t atxp1_storegpio1(struct device *dev, 209f24d548bSGuenter Roeck struct device_attribute *attr, const char *buf, 210f24d548bSGuenter Roeck size_t count) 2118d5d45fbSJean Delvare { 2128d5d45fbSJean Delvare struct atxp1_data *data; 2138d5d45fbSJean Delvare struct i2c_client *client; 214f24d548bSGuenter Roeck unsigned long value; 215f24d548bSGuenter Roeck int err; 2168d5d45fbSJean Delvare 2178d5d45fbSJean Delvare client = to_i2c_client(dev); 2188d5d45fbSJean Delvare data = atxp1_update_device(dev); 2198d5d45fbSJean Delvare 220f24d548bSGuenter Roeck err = kstrtoul(buf, 16, &value); 221f24d548bSGuenter Roeck if (err) 222f24d548bSGuenter Roeck return err; 2238d5d45fbSJean Delvare 2248d5d45fbSJean Delvare value &= ATXP1_GPIO1MASK; 2258d5d45fbSJean Delvare 2268d5d45fbSJean Delvare if (value != (data->reg.gpio1 & ATXP1_GPIO1MASK)) { 227f24d548bSGuenter Roeck dev_info(dev, "Writing 0x%x to GPIO1.\n", (unsigned int)value); 2288d5d45fbSJean Delvare 2298d5d45fbSJean Delvare i2c_smbus_write_byte_data(client, ATXP1_GPIO1, value); 2308d5d45fbSJean Delvare 2318d5d45fbSJean Delvare data->valid = 0; 2328d5d45fbSJean Delvare } 2338d5d45fbSJean Delvare 2348d5d45fbSJean Delvare return count; 2358d5d45fbSJean Delvare } 2368d5d45fbSJean Delvare 237f24d548bSGuenter Roeck /* 238f24d548bSGuenter Roeck * GPIO1 data register 239f24d548bSGuenter Roeck * unit: Four bit as hex (e.g. 0x0f) 2408d5d45fbSJean Delvare */ 2418d5d45fbSJean Delvare static DEVICE_ATTR(gpio1, S_IRUGO | S_IWUSR, atxp1_showgpio1, atxp1_storegpio1); 2428d5d45fbSJean Delvare 2438d5d45fbSJean Delvare /* sys file functions for GPIO2 */ 244f24d548bSGuenter Roeck static ssize_t atxp1_showgpio2(struct device *dev, 245f24d548bSGuenter Roeck struct device_attribute *attr, char *buf) 2468d5d45fbSJean Delvare { 2478d5d45fbSJean Delvare int size; 2488d5d45fbSJean Delvare struct atxp1_data *data; 2498d5d45fbSJean Delvare 2508d5d45fbSJean Delvare data = atxp1_update_device(dev); 2518d5d45fbSJean Delvare 2528d5d45fbSJean Delvare size = sprintf(buf, "0x%02x\n", data->reg.gpio2); 2538d5d45fbSJean Delvare 2548d5d45fbSJean Delvare return size; 2558d5d45fbSJean Delvare } 2568d5d45fbSJean Delvare 257f24d548bSGuenter Roeck static ssize_t atxp1_storegpio2(struct device *dev, 258f24d548bSGuenter Roeck struct device_attribute *attr, 259f24d548bSGuenter Roeck const char *buf, size_t count) 2608d5d45fbSJean Delvare { 261f24d548bSGuenter Roeck struct atxp1_data *data = atxp1_update_device(dev); 262f24d548bSGuenter Roeck struct i2c_client *client = to_i2c_client(dev); 263f24d548bSGuenter Roeck unsigned long value; 264f24d548bSGuenter Roeck int err; 2658d5d45fbSJean Delvare 266f24d548bSGuenter Roeck err = kstrtoul(buf, 16, &value); 267f24d548bSGuenter Roeck if (err) 268f24d548bSGuenter Roeck return err; 269f24d548bSGuenter Roeck value &= 0xff; 2708d5d45fbSJean Delvare 2718d5d45fbSJean Delvare if (value != data->reg.gpio2) { 272f24d548bSGuenter Roeck dev_info(dev, "Writing 0x%x to GPIO1.\n", (unsigned int)value); 2738d5d45fbSJean Delvare 2748d5d45fbSJean Delvare i2c_smbus_write_byte_data(client, ATXP1_GPIO2, value); 2758d5d45fbSJean Delvare 2768d5d45fbSJean Delvare data->valid = 0; 2778d5d45fbSJean Delvare } 2788d5d45fbSJean Delvare 2798d5d45fbSJean Delvare return count; 2808d5d45fbSJean Delvare } 2818d5d45fbSJean Delvare 282f24d548bSGuenter Roeck /* 283f24d548bSGuenter Roeck * GPIO2 data register 284f24d548bSGuenter Roeck * unit: Eight bit as hex (e.g. 0xff) 2858d5d45fbSJean Delvare */ 2868d5d45fbSJean Delvare static DEVICE_ATTR(gpio2, S_IRUGO | S_IWUSR, atxp1_showgpio2, atxp1_storegpio2); 2878d5d45fbSJean Delvare 288a5ebe668SJean Delvare static struct attribute *atxp1_attributes[] = { 289a5ebe668SJean Delvare &dev_attr_gpio1.attr, 290a5ebe668SJean Delvare &dev_attr_gpio2.attr, 291a5ebe668SJean Delvare &dev_attr_cpu0_vid.attr, 292a5ebe668SJean Delvare NULL 293a5ebe668SJean Delvare }; 294a5ebe668SJean Delvare 295a5ebe668SJean Delvare static const struct attribute_group atxp1_group = { 296a5ebe668SJean Delvare .attrs = atxp1_attributes, 297a5ebe668SJean Delvare }; 298a5ebe668SJean Delvare 2998d5d45fbSJean Delvare 30071163c7cSJean Delvare /* Return 0 if detection is successful, -ENODEV otherwise */ 301310ec792SJean Delvare static int atxp1_detect(struct i2c_client *new_client, 30271163c7cSJean Delvare struct i2c_board_info *info) 3038d5d45fbSJean Delvare { 30471163c7cSJean Delvare struct i2c_adapter *adapter = new_client->adapter; 3058d5d45fbSJean Delvare 3068d5d45fbSJean Delvare u8 temp; 3078d5d45fbSJean Delvare 3088d5d45fbSJean Delvare if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) 30971163c7cSJean Delvare return -ENODEV; 3108d5d45fbSJean Delvare 3118d5d45fbSJean Delvare /* Detect ATXP1, checking if vendor ID registers are all zero */ 3128d5d45fbSJean Delvare if (!((i2c_smbus_read_byte_data(new_client, 0x3e) == 0) && 3138d5d45fbSJean Delvare (i2c_smbus_read_byte_data(new_client, 0x3f) == 0) && 3148d5d45fbSJean Delvare (i2c_smbus_read_byte_data(new_client, 0xfe) == 0) && 31513b3c3faSJean Delvare (i2c_smbus_read_byte_data(new_client, 0xff) == 0))) 31613b3c3faSJean Delvare return -ENODEV; 3178d5d45fbSJean Delvare 318f24d548bSGuenter Roeck /* 319f24d548bSGuenter Roeck * No vendor ID, now checking if registers 0x10,0x11 (non-existent) 320f24d548bSGuenter Roeck * showing the same as register 0x00 321f24d548bSGuenter Roeck */ 3228d5d45fbSJean Delvare temp = i2c_smbus_read_byte_data(new_client, 0x00); 3238d5d45fbSJean Delvare 3248d5d45fbSJean Delvare if (!((i2c_smbus_read_byte_data(new_client, 0x10) == temp) && 3258d5d45fbSJean Delvare (i2c_smbus_read_byte_data(new_client, 0x11) == temp))) 32671163c7cSJean Delvare return -ENODEV; 32771163c7cSJean Delvare 32871163c7cSJean Delvare /* Get VRM */ 32971163c7cSJean Delvare temp = vid_which_vrm(); 33071163c7cSJean Delvare 33171163c7cSJean Delvare if ((temp != 90) && (temp != 91)) { 33271163c7cSJean Delvare dev_err(&adapter->dev, "atxp1: Not supporting VRM %d.%d\n", 33371163c7cSJean Delvare temp / 10, temp % 10); 33471163c7cSJean Delvare return -ENODEV; 33571163c7cSJean Delvare } 33671163c7cSJean Delvare 33771163c7cSJean Delvare strlcpy(info->type, "atxp1", I2C_NAME_SIZE); 33871163c7cSJean Delvare 33971163c7cSJean Delvare return 0; 34071163c7cSJean Delvare } 34171163c7cSJean Delvare 34271163c7cSJean Delvare static int atxp1_probe(struct i2c_client *new_client, 34371163c7cSJean Delvare const struct i2c_device_id *id) 34471163c7cSJean Delvare { 34571163c7cSJean Delvare struct atxp1_data *data; 34671163c7cSJean Delvare int err; 34771163c7cSJean Delvare 348*d466a353SGuenter Roeck data = devm_kzalloc(&new_client->dev, sizeof(struct atxp1_data), 349*d466a353SGuenter Roeck GFP_KERNEL); 350*d466a353SGuenter Roeck if (!data) 351*d466a353SGuenter Roeck return -ENOMEM; 3528d5d45fbSJean Delvare 3538d5d45fbSJean Delvare /* Get VRM */ 354303760b4SJean Delvare data->vrm = vid_which_vrm(); 3558d5d45fbSJean Delvare 35671163c7cSJean Delvare i2c_set_clientdata(new_client, data); 3578d5d45fbSJean Delvare data->valid = 0; 3588d5d45fbSJean Delvare 3599a61bf63SIngo Molnar mutex_init(&data->update_lock); 3608d5d45fbSJean Delvare 361a5ebe668SJean Delvare /* Register sysfs hooks */ 362f24d548bSGuenter Roeck err = sysfs_create_group(&new_client->dev.kobj, &atxp1_group); 363f24d548bSGuenter Roeck if (err) 364*d466a353SGuenter Roeck return err; 365a5ebe668SJean Delvare 3661beeffe4STony Jones data->hwmon_dev = hwmon_device_register(&new_client->dev); 3671beeffe4STony Jones if (IS_ERR(data->hwmon_dev)) { 3681beeffe4STony Jones err = PTR_ERR(data->hwmon_dev); 369a5ebe668SJean Delvare goto exit_remove_files; 370943b0830SMark M. Hoffman } 371943b0830SMark M. Hoffman 3728d5d45fbSJean Delvare dev_info(&new_client->dev, "Using VRM: %d.%d\n", 3738d5d45fbSJean Delvare data->vrm / 10, data->vrm % 10); 3748d5d45fbSJean Delvare 3758d5d45fbSJean Delvare return 0; 3768d5d45fbSJean Delvare 377a5ebe668SJean Delvare exit_remove_files: 378a5ebe668SJean Delvare sysfs_remove_group(&new_client->dev.kobj, &atxp1_group); 3798d5d45fbSJean Delvare return err; 3808d5d45fbSJean Delvare }; 3818d5d45fbSJean Delvare 38271163c7cSJean Delvare static int atxp1_remove(struct i2c_client *client) 3838d5d45fbSJean Delvare { 384943b0830SMark M. Hoffman struct atxp1_data *data = i2c_get_clientdata(client); 3858d5d45fbSJean Delvare 3861beeffe4STony Jones hwmon_device_unregister(data->hwmon_dev); 387a5ebe668SJean Delvare sysfs_remove_group(&client->dev.kobj, &atxp1_group); 388943b0830SMark M. Hoffman 38971163c7cSJean Delvare return 0; 3908d5d45fbSJean Delvare }; 3918d5d45fbSJean Delvare 392f0967eeaSAxel Lin module_i2c_driver(atxp1_driver); 393