18d5d45fbSJean Delvare /* 28d5d45fbSJean Delvare atxp1.c - kernel module for setting CPU VID and general purpose 38d5d45fbSJean Delvare I/Os using the Attansic ATXP1 chip. 48d5d45fbSJean Delvare 58d5d45fbSJean Delvare This program is free software; you can redistribute it and/or modify 68d5d45fbSJean Delvare it under the terms of the GNU General Public License as published by 78d5d45fbSJean Delvare the Free Software Foundation; either version 2 of the License, or 88d5d45fbSJean Delvare (at your option) any later version. 98d5d45fbSJean Delvare 108d5d45fbSJean Delvare This program is distributed in the hope that it will be useful, 118d5d45fbSJean Delvare but WITHOUT ANY WARRANTY; without even the implied warranty of 128d5d45fbSJean Delvare MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 138d5d45fbSJean Delvare GNU General Public License for more details. 148d5d45fbSJean Delvare 158d5d45fbSJean Delvare You should have received a copy of the GNU General Public License 168d5d45fbSJean Delvare along with this program; if not, write to the Free Software 178d5d45fbSJean Delvare Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 188d5d45fbSJean Delvare 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> 318d5d45fbSJean Delvare 328d5d45fbSJean Delvare MODULE_LICENSE("GPL"); 338d5d45fbSJean Delvare MODULE_DESCRIPTION("System voltages control via Attansic ATXP1"); 3413b3c3faSJean Delvare MODULE_VERSION("0.6.3"); 358d5d45fbSJean Delvare MODULE_AUTHOR("Sebastian Witt <se.witt@gmx.net>"); 368d5d45fbSJean Delvare 378d5d45fbSJean Delvare #define ATXP1_VID 0x00 388d5d45fbSJean Delvare #define ATXP1_CVID 0x01 398d5d45fbSJean Delvare #define ATXP1_GPIO1 0x06 408d5d45fbSJean Delvare #define ATXP1_GPIO2 0x0a 418d5d45fbSJean Delvare #define ATXP1_VIDENA 0x20 428d5d45fbSJean Delvare #define ATXP1_VIDMASK 0x1f 438d5d45fbSJean Delvare #define ATXP1_GPIO1MASK 0x0f 448d5d45fbSJean Delvare 4525e9c86dSMark M. Hoffman static const unsigned short normal_i2c[] = { 0x37, 0x4e, I2C_CLIENT_END }; 468d5d45fbSJean Delvare 47f4b50261SJean Delvare I2C_CLIENT_INSMOD_1(atxp1); 488d5d45fbSJean Delvare 4971163c7cSJean Delvare static int atxp1_probe(struct i2c_client *client, 5071163c7cSJean Delvare const struct i2c_device_id *id); 5171163c7cSJean Delvare static int atxp1_remove(struct i2c_client *client); 528d5d45fbSJean Delvare static struct atxp1_data * atxp1_update_device(struct device *dev); 53*310ec792SJean Delvare static int atxp1_detect(struct i2c_client *client, struct i2c_board_info *info); 5471163c7cSJean Delvare 5571163c7cSJean Delvare static const struct i2c_device_id atxp1_id[] = { 5671163c7cSJean Delvare { "atxp1", atxp1 }, 5771163c7cSJean Delvare { } 5871163c7cSJean Delvare }; 5971163c7cSJean Delvare MODULE_DEVICE_TABLE(i2c, atxp1_id); 608d5d45fbSJean Delvare 618d5d45fbSJean Delvare static struct i2c_driver atxp1_driver = { 6271163c7cSJean Delvare .class = I2C_CLASS_HWMON, 63cdaf7934SLaurent Riffard .driver = { 648d5d45fbSJean Delvare .name = "atxp1", 65cdaf7934SLaurent Riffard }, 6671163c7cSJean Delvare .probe = atxp1_probe, 6771163c7cSJean Delvare .remove = atxp1_remove, 6871163c7cSJean Delvare .id_table = atxp1_id, 6971163c7cSJean Delvare .detect = atxp1_detect, 7071163c7cSJean Delvare .address_data = &addr_data, 718d5d45fbSJean Delvare }; 728d5d45fbSJean Delvare 738d5d45fbSJean Delvare struct atxp1_data { 741beeffe4STony Jones struct device *hwmon_dev; 759a61bf63SIngo Molnar struct mutex update_lock; 768d5d45fbSJean Delvare unsigned long last_updated; 778d5d45fbSJean Delvare u8 valid; 788d5d45fbSJean Delvare struct { 798d5d45fbSJean Delvare u8 vid; /* VID output register */ 808d5d45fbSJean Delvare u8 cpu_vid; /* VID input from CPU */ 818d5d45fbSJean Delvare u8 gpio1; /* General purpose I/O register 1 */ 828d5d45fbSJean Delvare u8 gpio2; /* General purpose I/O register 2 */ 838d5d45fbSJean Delvare } reg; 848d5d45fbSJean Delvare u8 vrm; /* Detected CPU VRM */ 858d5d45fbSJean Delvare }; 868d5d45fbSJean Delvare 878d5d45fbSJean Delvare static struct atxp1_data * atxp1_update_device(struct device *dev) 888d5d45fbSJean Delvare { 898d5d45fbSJean Delvare struct i2c_client *client; 908d5d45fbSJean Delvare struct atxp1_data *data; 918d5d45fbSJean Delvare 928d5d45fbSJean Delvare client = to_i2c_client(dev); 938d5d45fbSJean Delvare data = i2c_get_clientdata(client); 948d5d45fbSJean Delvare 959a61bf63SIngo Molnar mutex_lock(&data->update_lock); 968d5d45fbSJean Delvare 970cacdf29SJean Delvare if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { 988d5d45fbSJean Delvare 998d5d45fbSJean Delvare /* Update local register data */ 1008d5d45fbSJean Delvare data->reg.vid = i2c_smbus_read_byte_data(client, ATXP1_VID); 1018d5d45fbSJean Delvare data->reg.cpu_vid = i2c_smbus_read_byte_data(client, 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 1108d5d45fbSJean Delvare return(data); 1118d5d45fbSJean Delvare } 1128d5d45fbSJean Delvare 1138d5d45fbSJean Delvare /* sys file functions for cpu0_vid */ 1148d5d45fbSJean Delvare static ssize_t atxp1_showvcore(struct device *dev, struct device_attribute *attr, char *buf) 1158d5d45fbSJean Delvare { 1168d5d45fbSJean Delvare int size; 1178d5d45fbSJean Delvare struct atxp1_data *data; 1188d5d45fbSJean Delvare 1198d5d45fbSJean Delvare data = atxp1_update_device(dev); 1208d5d45fbSJean Delvare 1218d5d45fbSJean Delvare size = sprintf(buf, "%d\n", vid_from_reg(data->reg.vid & ATXP1_VIDMASK, data->vrm)); 1228d5d45fbSJean Delvare 1238d5d45fbSJean Delvare return size; 1248d5d45fbSJean Delvare } 1258d5d45fbSJean Delvare 1268d5d45fbSJean Delvare static ssize_t atxp1_storevcore(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) 1278d5d45fbSJean Delvare { 1288d5d45fbSJean Delvare struct atxp1_data *data; 1298d5d45fbSJean Delvare struct i2c_client *client; 130c41bdb52SAlexey Dobriyan int vid, cvid; 1318d5d45fbSJean Delvare unsigned int vcore; 1328d5d45fbSJean Delvare 1338d5d45fbSJean Delvare client = to_i2c_client(dev); 1348d5d45fbSJean Delvare data = atxp1_update_device(dev); 1358d5d45fbSJean Delvare 1368d5d45fbSJean Delvare vcore = simple_strtoul(buf, NULL, 10); 1378d5d45fbSJean Delvare vcore /= 25; 1388d5d45fbSJean Delvare vcore *= 25; 1398d5d45fbSJean Delvare 1408d5d45fbSJean Delvare /* Calculate VID */ 1418d5d45fbSJean Delvare vid = vid_to_reg(vcore, data->vrm); 1428d5d45fbSJean Delvare 1438d5d45fbSJean Delvare if (vid < 0) { 1448d5d45fbSJean Delvare dev_err(dev, "VID calculation failed.\n"); 1458d5d45fbSJean Delvare return -1; 1468d5d45fbSJean Delvare } 1478d5d45fbSJean Delvare 1488d5d45fbSJean Delvare /* If output enabled, use control register value. Otherwise original CPU VID */ 1498d5d45fbSJean Delvare if (data->reg.vid & ATXP1_VIDENA) 1508d5d45fbSJean Delvare cvid = data->reg.vid & ATXP1_VIDMASK; 1518d5d45fbSJean Delvare else 1528d5d45fbSJean Delvare cvid = data->reg.cpu_vid; 1538d5d45fbSJean Delvare 1548d5d45fbSJean Delvare /* Nothing changed, aborting */ 1558d5d45fbSJean Delvare if (vid == cvid) 1568d5d45fbSJean Delvare return count; 1578d5d45fbSJean Delvare 1588d5d45fbSJean Delvare dev_dbg(dev, "Setting VCore to %d mV (0x%02x)\n", vcore, vid); 1598d5d45fbSJean Delvare 1608d5d45fbSJean Delvare /* Write every 25 mV step to increase stability */ 1618d5d45fbSJean Delvare if (cvid > vid) { 1628d5d45fbSJean Delvare for (; cvid >= vid; cvid--) { 1638d5d45fbSJean Delvare i2c_smbus_write_byte_data(client, ATXP1_VID, cvid | ATXP1_VIDENA); 1648d5d45fbSJean Delvare } 1658d5d45fbSJean Delvare } 1668d5d45fbSJean Delvare else { 1678d5d45fbSJean Delvare for (; cvid <= vid; cvid++) { 1688d5d45fbSJean Delvare i2c_smbus_write_byte_data(client, ATXP1_VID, cvid | ATXP1_VIDENA); 1698d5d45fbSJean Delvare } 1708d5d45fbSJean Delvare } 1718d5d45fbSJean Delvare 1728d5d45fbSJean Delvare data->valid = 0; 1738d5d45fbSJean Delvare 1748d5d45fbSJean Delvare return count; 1758d5d45fbSJean Delvare } 1768d5d45fbSJean Delvare 1778d5d45fbSJean Delvare /* CPU core reference voltage 1788d5d45fbSJean Delvare unit: millivolt 1798d5d45fbSJean Delvare */ 1808d5d45fbSJean Delvare static DEVICE_ATTR(cpu0_vid, S_IRUGO | S_IWUSR, atxp1_showvcore, atxp1_storevcore); 1818d5d45fbSJean Delvare 1828d5d45fbSJean Delvare /* sys file functions for GPIO1 */ 1838d5d45fbSJean Delvare static ssize_t atxp1_showgpio1(struct device *dev, struct device_attribute *attr, char *buf) 1848d5d45fbSJean Delvare { 1858d5d45fbSJean Delvare int size; 1868d5d45fbSJean Delvare struct atxp1_data *data; 1878d5d45fbSJean Delvare 1888d5d45fbSJean Delvare data = atxp1_update_device(dev); 1898d5d45fbSJean Delvare 1908d5d45fbSJean Delvare size = sprintf(buf, "0x%02x\n", data->reg.gpio1 & ATXP1_GPIO1MASK); 1918d5d45fbSJean Delvare 1928d5d45fbSJean Delvare return size; 1938d5d45fbSJean Delvare } 1948d5d45fbSJean Delvare 1958d5d45fbSJean Delvare static ssize_t atxp1_storegpio1(struct device *dev, struct device_attribute *attr, const char*buf, size_t count) 1968d5d45fbSJean Delvare { 1978d5d45fbSJean Delvare struct atxp1_data *data; 1988d5d45fbSJean Delvare struct i2c_client *client; 1998d5d45fbSJean Delvare unsigned int value; 2008d5d45fbSJean Delvare 2018d5d45fbSJean Delvare client = to_i2c_client(dev); 2028d5d45fbSJean Delvare data = atxp1_update_device(dev); 2038d5d45fbSJean Delvare 2048d5d45fbSJean Delvare value = simple_strtoul(buf, NULL, 16); 2058d5d45fbSJean Delvare 2068d5d45fbSJean Delvare value &= ATXP1_GPIO1MASK; 2078d5d45fbSJean Delvare 2088d5d45fbSJean Delvare if (value != (data->reg.gpio1 & ATXP1_GPIO1MASK)) { 2098d5d45fbSJean Delvare dev_info(dev, "Writing 0x%x to GPIO1.\n", value); 2108d5d45fbSJean Delvare 2118d5d45fbSJean Delvare i2c_smbus_write_byte_data(client, ATXP1_GPIO1, value); 2128d5d45fbSJean Delvare 2138d5d45fbSJean Delvare data->valid = 0; 2148d5d45fbSJean Delvare } 2158d5d45fbSJean Delvare 2168d5d45fbSJean Delvare return count; 2178d5d45fbSJean Delvare } 2188d5d45fbSJean Delvare 2198d5d45fbSJean Delvare /* GPIO1 data register 2208d5d45fbSJean Delvare unit: Four bit as hex (e.g. 0x0f) 2218d5d45fbSJean Delvare */ 2228d5d45fbSJean Delvare static DEVICE_ATTR(gpio1, S_IRUGO | S_IWUSR, atxp1_showgpio1, atxp1_storegpio1); 2238d5d45fbSJean Delvare 2248d5d45fbSJean Delvare /* sys file functions for GPIO2 */ 2258d5d45fbSJean Delvare static ssize_t atxp1_showgpio2(struct device *dev, struct device_attribute *attr, char *buf) 2268d5d45fbSJean Delvare { 2278d5d45fbSJean Delvare int size; 2288d5d45fbSJean Delvare struct atxp1_data *data; 2298d5d45fbSJean Delvare 2308d5d45fbSJean Delvare data = atxp1_update_device(dev); 2318d5d45fbSJean Delvare 2328d5d45fbSJean Delvare size = sprintf(buf, "0x%02x\n", data->reg.gpio2); 2338d5d45fbSJean Delvare 2348d5d45fbSJean Delvare return size; 2358d5d45fbSJean Delvare } 2368d5d45fbSJean Delvare 2378d5d45fbSJean Delvare static ssize_t atxp1_storegpio2(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) 2388d5d45fbSJean Delvare { 2398d5d45fbSJean Delvare struct atxp1_data *data; 2408d5d45fbSJean Delvare struct i2c_client *client; 2418d5d45fbSJean Delvare unsigned int value; 2428d5d45fbSJean Delvare 2438d5d45fbSJean Delvare client = to_i2c_client(dev); 2448d5d45fbSJean Delvare data = atxp1_update_device(dev); 2458d5d45fbSJean Delvare 2468d5d45fbSJean Delvare value = simple_strtoul(buf, NULL, 16) & 0xff; 2478d5d45fbSJean Delvare 2488d5d45fbSJean Delvare if (value != data->reg.gpio2) { 2498d5d45fbSJean Delvare dev_info(dev, "Writing 0x%x to GPIO1.\n", value); 2508d5d45fbSJean Delvare 2518d5d45fbSJean Delvare i2c_smbus_write_byte_data(client, ATXP1_GPIO2, value); 2528d5d45fbSJean Delvare 2538d5d45fbSJean Delvare data->valid = 0; 2548d5d45fbSJean Delvare } 2558d5d45fbSJean Delvare 2568d5d45fbSJean Delvare return count; 2578d5d45fbSJean Delvare } 2588d5d45fbSJean Delvare 2598d5d45fbSJean Delvare /* GPIO2 data register 2608d5d45fbSJean Delvare unit: Eight bit as hex (e.g. 0xff) 2618d5d45fbSJean Delvare */ 2628d5d45fbSJean Delvare static DEVICE_ATTR(gpio2, S_IRUGO | S_IWUSR, atxp1_showgpio2, atxp1_storegpio2); 2638d5d45fbSJean Delvare 264a5ebe668SJean Delvare static struct attribute *atxp1_attributes[] = { 265a5ebe668SJean Delvare &dev_attr_gpio1.attr, 266a5ebe668SJean Delvare &dev_attr_gpio2.attr, 267a5ebe668SJean Delvare &dev_attr_cpu0_vid.attr, 268a5ebe668SJean Delvare NULL 269a5ebe668SJean Delvare }; 270a5ebe668SJean Delvare 271a5ebe668SJean Delvare static const struct attribute_group atxp1_group = { 272a5ebe668SJean Delvare .attrs = atxp1_attributes, 273a5ebe668SJean Delvare }; 274a5ebe668SJean Delvare 2758d5d45fbSJean Delvare 27671163c7cSJean Delvare /* Return 0 if detection is successful, -ENODEV otherwise */ 277*310ec792SJean Delvare static int atxp1_detect(struct i2c_client *new_client, 27871163c7cSJean Delvare struct i2c_board_info *info) 2798d5d45fbSJean Delvare { 28071163c7cSJean Delvare struct i2c_adapter *adapter = new_client->adapter; 2818d5d45fbSJean Delvare 2828d5d45fbSJean Delvare u8 temp; 2838d5d45fbSJean Delvare 2848d5d45fbSJean Delvare if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) 28571163c7cSJean Delvare return -ENODEV; 2868d5d45fbSJean Delvare 2878d5d45fbSJean Delvare /* Detect ATXP1, checking if vendor ID registers are all zero */ 2888d5d45fbSJean Delvare if (!((i2c_smbus_read_byte_data(new_client, 0x3e) == 0) && 2898d5d45fbSJean Delvare (i2c_smbus_read_byte_data(new_client, 0x3f) == 0) && 2908d5d45fbSJean Delvare (i2c_smbus_read_byte_data(new_client, 0xfe) == 0) && 29113b3c3faSJean Delvare (i2c_smbus_read_byte_data(new_client, 0xff) == 0))) 29213b3c3faSJean Delvare return -ENODEV; 2938d5d45fbSJean Delvare 2948d5d45fbSJean Delvare /* No vendor ID, now checking if registers 0x10,0x11 (non-existent) 2958d5d45fbSJean Delvare * showing the same as register 0x00 */ 2968d5d45fbSJean Delvare temp = i2c_smbus_read_byte_data(new_client, 0x00); 2978d5d45fbSJean Delvare 2988d5d45fbSJean Delvare if (!((i2c_smbus_read_byte_data(new_client, 0x10) == temp) && 2998d5d45fbSJean Delvare (i2c_smbus_read_byte_data(new_client, 0x11) == temp))) 30071163c7cSJean Delvare return -ENODEV; 30171163c7cSJean Delvare 30271163c7cSJean Delvare /* Get VRM */ 30371163c7cSJean Delvare temp = vid_which_vrm(); 30471163c7cSJean Delvare 30571163c7cSJean Delvare if ((temp != 90) && (temp != 91)) { 30671163c7cSJean Delvare dev_err(&adapter->dev, "atxp1: Not supporting VRM %d.%d\n", 30771163c7cSJean Delvare temp / 10, temp % 10); 30871163c7cSJean Delvare return -ENODEV; 30971163c7cSJean Delvare } 31071163c7cSJean Delvare 31171163c7cSJean Delvare strlcpy(info->type, "atxp1", I2C_NAME_SIZE); 31271163c7cSJean Delvare 31371163c7cSJean Delvare return 0; 31471163c7cSJean Delvare } 31571163c7cSJean Delvare 31671163c7cSJean Delvare static int atxp1_probe(struct i2c_client *new_client, 31771163c7cSJean Delvare const struct i2c_device_id *id) 31871163c7cSJean Delvare { 31971163c7cSJean Delvare struct atxp1_data *data; 32071163c7cSJean Delvare int err; 32171163c7cSJean Delvare 32271163c7cSJean Delvare data = kzalloc(sizeof(struct atxp1_data), GFP_KERNEL); 32371163c7cSJean Delvare if (!data) { 32471163c7cSJean Delvare err = -ENOMEM; 32571163c7cSJean Delvare goto exit; 3268d5d45fbSJean Delvare } 3278d5d45fbSJean Delvare 3288d5d45fbSJean Delvare /* Get VRM */ 329303760b4SJean Delvare data->vrm = vid_which_vrm(); 3308d5d45fbSJean Delvare 33171163c7cSJean Delvare i2c_set_clientdata(new_client, data); 3328d5d45fbSJean Delvare data->valid = 0; 3338d5d45fbSJean Delvare 3349a61bf63SIngo Molnar mutex_init(&data->update_lock); 3358d5d45fbSJean Delvare 336a5ebe668SJean Delvare /* Register sysfs hooks */ 337a5ebe668SJean Delvare if ((err = sysfs_create_group(&new_client->dev.kobj, &atxp1_group))) 33871163c7cSJean Delvare goto exit_free; 339a5ebe668SJean Delvare 3401beeffe4STony Jones data->hwmon_dev = hwmon_device_register(&new_client->dev); 3411beeffe4STony Jones if (IS_ERR(data->hwmon_dev)) { 3421beeffe4STony Jones err = PTR_ERR(data->hwmon_dev); 343a5ebe668SJean Delvare goto exit_remove_files; 344943b0830SMark M. Hoffman } 345943b0830SMark M. Hoffman 3468d5d45fbSJean Delvare dev_info(&new_client->dev, "Using VRM: %d.%d\n", 3478d5d45fbSJean Delvare data->vrm / 10, data->vrm % 10); 3488d5d45fbSJean Delvare 3498d5d45fbSJean Delvare return 0; 3508d5d45fbSJean Delvare 351a5ebe668SJean Delvare exit_remove_files: 352a5ebe668SJean Delvare sysfs_remove_group(&new_client->dev.kobj, &atxp1_group); 3538d5d45fbSJean Delvare exit_free: 3548d5d45fbSJean Delvare kfree(data); 3558d5d45fbSJean Delvare exit: 3568d5d45fbSJean Delvare return err; 3578d5d45fbSJean Delvare }; 3588d5d45fbSJean Delvare 35971163c7cSJean Delvare static int atxp1_remove(struct i2c_client *client) 3608d5d45fbSJean Delvare { 361943b0830SMark M. Hoffman struct atxp1_data * data = i2c_get_clientdata(client); 3628d5d45fbSJean Delvare 3631beeffe4STony Jones hwmon_device_unregister(data->hwmon_dev); 364a5ebe668SJean Delvare sysfs_remove_group(&client->dev.kobj, &atxp1_group); 365943b0830SMark M. Hoffman 366943b0830SMark M. Hoffman kfree(data); 3678d5d45fbSJean Delvare 36871163c7cSJean Delvare return 0; 3698d5d45fbSJean Delvare }; 3708d5d45fbSJean Delvare 3718d5d45fbSJean Delvare static int __init atxp1_init(void) 3728d5d45fbSJean Delvare { 3738d5d45fbSJean Delvare return i2c_add_driver(&atxp1_driver); 3748d5d45fbSJean Delvare }; 3758d5d45fbSJean Delvare 3768d5d45fbSJean Delvare static void __exit atxp1_exit(void) 3778d5d45fbSJean Delvare { 3788d5d45fbSJean Delvare i2c_del_driver(&atxp1_driver); 3798d5d45fbSJean Delvare }; 3808d5d45fbSJean Delvare 3818d5d45fbSJean Delvare module_init(atxp1_init); 3828d5d45fbSJean Delvare module_exit(atxp1_exit); 383