1*8d5d45fbSJean Delvare /* 2*8d5d45fbSJean Delvare atxp1.c - kernel module for setting CPU VID and general purpose 3*8d5d45fbSJean Delvare I/Os using the Attansic ATXP1 chip. 4*8d5d45fbSJean Delvare 5*8d5d45fbSJean Delvare This program is free software; you can redistribute it and/or modify 6*8d5d45fbSJean Delvare it under the terms of the GNU General Public License as published by 7*8d5d45fbSJean Delvare the Free Software Foundation; either version 2 of the License, or 8*8d5d45fbSJean Delvare (at your option) any later version. 9*8d5d45fbSJean Delvare 10*8d5d45fbSJean Delvare This program is distributed in the hope that it will be useful, 11*8d5d45fbSJean Delvare but WITHOUT ANY WARRANTY; without even the implied warranty of 12*8d5d45fbSJean Delvare MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13*8d5d45fbSJean Delvare GNU General Public License for more details. 14*8d5d45fbSJean Delvare 15*8d5d45fbSJean Delvare You should have received a copy of the GNU General Public License 16*8d5d45fbSJean Delvare along with this program; if not, write to the Free Software 17*8d5d45fbSJean Delvare Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 18*8d5d45fbSJean Delvare 19*8d5d45fbSJean Delvare */ 20*8d5d45fbSJean Delvare 21*8d5d45fbSJean Delvare #include <linux/kernel.h> 22*8d5d45fbSJean Delvare #include <linux/init.h> 23*8d5d45fbSJean Delvare #include <linux/module.h> 24*8d5d45fbSJean Delvare #include <linux/i2c.h> 25*8d5d45fbSJean Delvare #include <linux/i2c-sensor.h> 26*8d5d45fbSJean Delvare #include <linux/i2c-vid.h> 27*8d5d45fbSJean Delvare 28*8d5d45fbSJean Delvare MODULE_LICENSE("GPL"); 29*8d5d45fbSJean Delvare MODULE_DESCRIPTION("System voltages control via Attansic ATXP1"); 30*8d5d45fbSJean Delvare MODULE_VERSION("0.6.2"); 31*8d5d45fbSJean Delvare MODULE_AUTHOR("Sebastian Witt <se.witt@gmx.net>"); 32*8d5d45fbSJean Delvare 33*8d5d45fbSJean Delvare #define ATXP1_VID 0x00 34*8d5d45fbSJean Delvare #define ATXP1_CVID 0x01 35*8d5d45fbSJean Delvare #define ATXP1_GPIO1 0x06 36*8d5d45fbSJean Delvare #define ATXP1_GPIO2 0x0a 37*8d5d45fbSJean Delvare #define ATXP1_VIDENA 0x20 38*8d5d45fbSJean Delvare #define ATXP1_VIDMASK 0x1f 39*8d5d45fbSJean Delvare #define ATXP1_GPIO1MASK 0x0f 40*8d5d45fbSJean Delvare 41*8d5d45fbSJean Delvare static unsigned short normal_i2c[] = { 0x37, 0x4e, I2C_CLIENT_END }; 42*8d5d45fbSJean Delvare static unsigned int normal_isa[] = { I2C_CLIENT_ISA_END }; 43*8d5d45fbSJean Delvare 44*8d5d45fbSJean Delvare SENSORS_INSMOD_1(atxp1); 45*8d5d45fbSJean Delvare 46*8d5d45fbSJean Delvare static int atxp1_attach_adapter(struct i2c_adapter * adapter); 47*8d5d45fbSJean Delvare static int atxp1_detach_client(struct i2c_client * client); 48*8d5d45fbSJean Delvare static struct atxp1_data * atxp1_update_device(struct device *dev); 49*8d5d45fbSJean Delvare static int atxp1_detect(struct i2c_adapter *adapter, int address, int kind); 50*8d5d45fbSJean Delvare 51*8d5d45fbSJean Delvare static struct i2c_driver atxp1_driver = { 52*8d5d45fbSJean Delvare .owner = THIS_MODULE, 53*8d5d45fbSJean Delvare .name = "atxp1", 54*8d5d45fbSJean Delvare .flags = I2C_DF_NOTIFY, 55*8d5d45fbSJean Delvare .attach_adapter = atxp1_attach_adapter, 56*8d5d45fbSJean Delvare .detach_client = atxp1_detach_client, 57*8d5d45fbSJean Delvare }; 58*8d5d45fbSJean Delvare 59*8d5d45fbSJean Delvare struct atxp1_data { 60*8d5d45fbSJean Delvare struct i2c_client client; 61*8d5d45fbSJean Delvare struct semaphore update_lock; 62*8d5d45fbSJean Delvare unsigned long last_updated; 63*8d5d45fbSJean Delvare u8 valid; 64*8d5d45fbSJean Delvare struct { 65*8d5d45fbSJean Delvare u8 vid; /* VID output register */ 66*8d5d45fbSJean Delvare u8 cpu_vid; /* VID input from CPU */ 67*8d5d45fbSJean Delvare u8 gpio1; /* General purpose I/O register 1 */ 68*8d5d45fbSJean Delvare u8 gpio2; /* General purpose I/O register 2 */ 69*8d5d45fbSJean Delvare } reg; 70*8d5d45fbSJean Delvare u8 vrm; /* Detected CPU VRM */ 71*8d5d45fbSJean Delvare }; 72*8d5d45fbSJean Delvare 73*8d5d45fbSJean Delvare static struct atxp1_data * atxp1_update_device(struct device *dev) 74*8d5d45fbSJean Delvare { 75*8d5d45fbSJean Delvare struct i2c_client *client; 76*8d5d45fbSJean Delvare struct atxp1_data *data; 77*8d5d45fbSJean Delvare 78*8d5d45fbSJean Delvare client = to_i2c_client(dev); 79*8d5d45fbSJean Delvare data = i2c_get_clientdata(client); 80*8d5d45fbSJean Delvare 81*8d5d45fbSJean Delvare down(&data->update_lock); 82*8d5d45fbSJean Delvare 83*8d5d45fbSJean Delvare if ((jiffies - data->last_updated > HZ) || 84*8d5d45fbSJean Delvare (jiffies < data->last_updated) || 85*8d5d45fbSJean Delvare !data->valid) { 86*8d5d45fbSJean Delvare 87*8d5d45fbSJean Delvare /* Update local register data */ 88*8d5d45fbSJean Delvare data->reg.vid = i2c_smbus_read_byte_data(client, ATXP1_VID); 89*8d5d45fbSJean Delvare data->reg.cpu_vid = i2c_smbus_read_byte_data(client, ATXP1_CVID); 90*8d5d45fbSJean Delvare data->reg.gpio1 = i2c_smbus_read_byte_data(client, ATXP1_GPIO1); 91*8d5d45fbSJean Delvare data->reg.gpio2 = i2c_smbus_read_byte_data(client, ATXP1_GPIO2); 92*8d5d45fbSJean Delvare 93*8d5d45fbSJean Delvare data->valid = 1; 94*8d5d45fbSJean Delvare } 95*8d5d45fbSJean Delvare 96*8d5d45fbSJean Delvare up(&data->update_lock); 97*8d5d45fbSJean Delvare 98*8d5d45fbSJean Delvare return(data); 99*8d5d45fbSJean Delvare } 100*8d5d45fbSJean Delvare 101*8d5d45fbSJean Delvare /* sys file functions for cpu0_vid */ 102*8d5d45fbSJean Delvare static ssize_t atxp1_showvcore(struct device *dev, struct device_attribute *attr, char *buf) 103*8d5d45fbSJean Delvare { 104*8d5d45fbSJean Delvare int size; 105*8d5d45fbSJean Delvare struct atxp1_data *data; 106*8d5d45fbSJean Delvare 107*8d5d45fbSJean Delvare data = atxp1_update_device(dev); 108*8d5d45fbSJean Delvare 109*8d5d45fbSJean Delvare size = sprintf(buf, "%d\n", vid_from_reg(data->reg.vid & ATXP1_VIDMASK, data->vrm)); 110*8d5d45fbSJean Delvare 111*8d5d45fbSJean Delvare return size; 112*8d5d45fbSJean Delvare } 113*8d5d45fbSJean Delvare 114*8d5d45fbSJean Delvare static ssize_t atxp1_storevcore(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) 115*8d5d45fbSJean Delvare { 116*8d5d45fbSJean Delvare struct atxp1_data *data; 117*8d5d45fbSJean Delvare struct i2c_client *client; 118*8d5d45fbSJean Delvare char vid; 119*8d5d45fbSJean Delvare char cvid; 120*8d5d45fbSJean Delvare unsigned int vcore; 121*8d5d45fbSJean Delvare 122*8d5d45fbSJean Delvare client = to_i2c_client(dev); 123*8d5d45fbSJean Delvare data = atxp1_update_device(dev); 124*8d5d45fbSJean Delvare 125*8d5d45fbSJean Delvare vcore = simple_strtoul(buf, NULL, 10); 126*8d5d45fbSJean Delvare vcore /= 25; 127*8d5d45fbSJean Delvare vcore *= 25; 128*8d5d45fbSJean Delvare 129*8d5d45fbSJean Delvare /* Calculate VID */ 130*8d5d45fbSJean Delvare vid = vid_to_reg(vcore, data->vrm); 131*8d5d45fbSJean Delvare 132*8d5d45fbSJean Delvare if (vid < 0) { 133*8d5d45fbSJean Delvare dev_err(dev, "VID calculation failed.\n"); 134*8d5d45fbSJean Delvare return -1; 135*8d5d45fbSJean Delvare } 136*8d5d45fbSJean Delvare 137*8d5d45fbSJean Delvare /* If output enabled, use control register value. Otherwise original CPU VID */ 138*8d5d45fbSJean Delvare if (data->reg.vid & ATXP1_VIDENA) 139*8d5d45fbSJean Delvare cvid = data->reg.vid & ATXP1_VIDMASK; 140*8d5d45fbSJean Delvare else 141*8d5d45fbSJean Delvare cvid = data->reg.cpu_vid; 142*8d5d45fbSJean Delvare 143*8d5d45fbSJean Delvare /* Nothing changed, aborting */ 144*8d5d45fbSJean Delvare if (vid == cvid) 145*8d5d45fbSJean Delvare return count; 146*8d5d45fbSJean Delvare 147*8d5d45fbSJean Delvare dev_dbg(dev, "Setting VCore to %d mV (0x%02x)\n", vcore, vid); 148*8d5d45fbSJean Delvare 149*8d5d45fbSJean Delvare /* Write every 25 mV step to increase stability */ 150*8d5d45fbSJean Delvare if (cvid > vid) { 151*8d5d45fbSJean Delvare for (; cvid >= vid; cvid--) { 152*8d5d45fbSJean Delvare i2c_smbus_write_byte_data(client, ATXP1_VID, cvid | ATXP1_VIDENA); 153*8d5d45fbSJean Delvare } 154*8d5d45fbSJean Delvare } 155*8d5d45fbSJean Delvare else { 156*8d5d45fbSJean Delvare for (; cvid <= vid; cvid++) { 157*8d5d45fbSJean Delvare i2c_smbus_write_byte_data(client, ATXP1_VID, cvid | ATXP1_VIDENA); 158*8d5d45fbSJean Delvare } 159*8d5d45fbSJean Delvare } 160*8d5d45fbSJean Delvare 161*8d5d45fbSJean Delvare data->valid = 0; 162*8d5d45fbSJean Delvare 163*8d5d45fbSJean Delvare return count; 164*8d5d45fbSJean Delvare } 165*8d5d45fbSJean Delvare 166*8d5d45fbSJean Delvare /* CPU core reference voltage 167*8d5d45fbSJean Delvare unit: millivolt 168*8d5d45fbSJean Delvare */ 169*8d5d45fbSJean Delvare static DEVICE_ATTR(cpu0_vid, S_IRUGO | S_IWUSR, atxp1_showvcore, atxp1_storevcore); 170*8d5d45fbSJean Delvare 171*8d5d45fbSJean Delvare /* sys file functions for GPIO1 */ 172*8d5d45fbSJean Delvare static ssize_t atxp1_showgpio1(struct device *dev, struct device_attribute *attr, char *buf) 173*8d5d45fbSJean Delvare { 174*8d5d45fbSJean Delvare int size; 175*8d5d45fbSJean Delvare struct atxp1_data *data; 176*8d5d45fbSJean Delvare 177*8d5d45fbSJean Delvare data = atxp1_update_device(dev); 178*8d5d45fbSJean Delvare 179*8d5d45fbSJean Delvare size = sprintf(buf, "0x%02x\n", data->reg.gpio1 & ATXP1_GPIO1MASK); 180*8d5d45fbSJean Delvare 181*8d5d45fbSJean Delvare return size; 182*8d5d45fbSJean Delvare } 183*8d5d45fbSJean Delvare 184*8d5d45fbSJean Delvare static ssize_t atxp1_storegpio1(struct device *dev, struct device_attribute *attr, const char*buf, size_t count) 185*8d5d45fbSJean Delvare { 186*8d5d45fbSJean Delvare struct atxp1_data *data; 187*8d5d45fbSJean Delvare struct i2c_client *client; 188*8d5d45fbSJean Delvare unsigned int value; 189*8d5d45fbSJean Delvare 190*8d5d45fbSJean Delvare client = to_i2c_client(dev); 191*8d5d45fbSJean Delvare data = atxp1_update_device(dev); 192*8d5d45fbSJean Delvare 193*8d5d45fbSJean Delvare value = simple_strtoul(buf, NULL, 16); 194*8d5d45fbSJean Delvare 195*8d5d45fbSJean Delvare value &= ATXP1_GPIO1MASK; 196*8d5d45fbSJean Delvare 197*8d5d45fbSJean Delvare if (value != (data->reg.gpio1 & ATXP1_GPIO1MASK)) { 198*8d5d45fbSJean Delvare dev_info(dev, "Writing 0x%x to GPIO1.\n", value); 199*8d5d45fbSJean Delvare 200*8d5d45fbSJean Delvare i2c_smbus_write_byte_data(client, ATXP1_GPIO1, value); 201*8d5d45fbSJean Delvare 202*8d5d45fbSJean Delvare data->valid = 0; 203*8d5d45fbSJean Delvare } 204*8d5d45fbSJean Delvare 205*8d5d45fbSJean Delvare return count; 206*8d5d45fbSJean Delvare } 207*8d5d45fbSJean Delvare 208*8d5d45fbSJean Delvare /* GPIO1 data register 209*8d5d45fbSJean Delvare unit: Four bit as hex (e.g. 0x0f) 210*8d5d45fbSJean Delvare */ 211*8d5d45fbSJean Delvare static DEVICE_ATTR(gpio1, S_IRUGO | S_IWUSR, atxp1_showgpio1, atxp1_storegpio1); 212*8d5d45fbSJean Delvare 213*8d5d45fbSJean Delvare /* sys file functions for GPIO2 */ 214*8d5d45fbSJean Delvare static ssize_t atxp1_showgpio2(struct device *dev, struct device_attribute *attr, char *buf) 215*8d5d45fbSJean Delvare { 216*8d5d45fbSJean Delvare int size; 217*8d5d45fbSJean Delvare struct atxp1_data *data; 218*8d5d45fbSJean Delvare 219*8d5d45fbSJean Delvare data = atxp1_update_device(dev); 220*8d5d45fbSJean Delvare 221*8d5d45fbSJean Delvare size = sprintf(buf, "0x%02x\n", data->reg.gpio2); 222*8d5d45fbSJean Delvare 223*8d5d45fbSJean Delvare return size; 224*8d5d45fbSJean Delvare } 225*8d5d45fbSJean Delvare 226*8d5d45fbSJean Delvare static ssize_t atxp1_storegpio2(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) 227*8d5d45fbSJean Delvare { 228*8d5d45fbSJean Delvare struct atxp1_data *data; 229*8d5d45fbSJean Delvare struct i2c_client *client; 230*8d5d45fbSJean Delvare unsigned int value; 231*8d5d45fbSJean Delvare 232*8d5d45fbSJean Delvare client = to_i2c_client(dev); 233*8d5d45fbSJean Delvare data = atxp1_update_device(dev); 234*8d5d45fbSJean Delvare 235*8d5d45fbSJean Delvare value = simple_strtoul(buf, NULL, 16) & 0xff; 236*8d5d45fbSJean Delvare 237*8d5d45fbSJean Delvare if (value != data->reg.gpio2) { 238*8d5d45fbSJean Delvare dev_info(dev, "Writing 0x%x to GPIO1.\n", value); 239*8d5d45fbSJean Delvare 240*8d5d45fbSJean Delvare i2c_smbus_write_byte_data(client, ATXP1_GPIO2, value); 241*8d5d45fbSJean Delvare 242*8d5d45fbSJean Delvare data->valid = 0; 243*8d5d45fbSJean Delvare } 244*8d5d45fbSJean Delvare 245*8d5d45fbSJean Delvare return count; 246*8d5d45fbSJean Delvare } 247*8d5d45fbSJean Delvare 248*8d5d45fbSJean Delvare /* GPIO2 data register 249*8d5d45fbSJean Delvare unit: Eight bit as hex (e.g. 0xff) 250*8d5d45fbSJean Delvare */ 251*8d5d45fbSJean Delvare static DEVICE_ATTR(gpio2, S_IRUGO | S_IWUSR, atxp1_showgpio2, atxp1_storegpio2); 252*8d5d45fbSJean Delvare 253*8d5d45fbSJean Delvare 254*8d5d45fbSJean Delvare static int atxp1_attach_adapter(struct i2c_adapter *adapter) 255*8d5d45fbSJean Delvare { 256*8d5d45fbSJean Delvare return i2c_detect(adapter, &addr_data, &atxp1_detect); 257*8d5d45fbSJean Delvare }; 258*8d5d45fbSJean Delvare 259*8d5d45fbSJean Delvare static int atxp1_detect(struct i2c_adapter *adapter, int address, int kind) 260*8d5d45fbSJean Delvare { 261*8d5d45fbSJean Delvare struct i2c_client * new_client; 262*8d5d45fbSJean Delvare struct atxp1_data * data; 263*8d5d45fbSJean Delvare int err = 0; 264*8d5d45fbSJean Delvare u8 temp; 265*8d5d45fbSJean Delvare 266*8d5d45fbSJean Delvare if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) 267*8d5d45fbSJean Delvare goto exit; 268*8d5d45fbSJean Delvare 269*8d5d45fbSJean Delvare if (!(data = kmalloc(sizeof(struct atxp1_data), GFP_KERNEL))) { 270*8d5d45fbSJean Delvare err = -ENOMEM; 271*8d5d45fbSJean Delvare goto exit; 272*8d5d45fbSJean Delvare } 273*8d5d45fbSJean Delvare 274*8d5d45fbSJean Delvare memset(data, 0, sizeof(struct atxp1_data)); 275*8d5d45fbSJean Delvare new_client = &data->client; 276*8d5d45fbSJean Delvare i2c_set_clientdata(new_client, data); 277*8d5d45fbSJean Delvare 278*8d5d45fbSJean Delvare new_client->addr = address; 279*8d5d45fbSJean Delvare new_client->adapter = adapter; 280*8d5d45fbSJean Delvare new_client->driver = &atxp1_driver; 281*8d5d45fbSJean Delvare new_client->flags = 0; 282*8d5d45fbSJean Delvare 283*8d5d45fbSJean Delvare /* Detect ATXP1, checking if vendor ID registers are all zero */ 284*8d5d45fbSJean Delvare if (!((i2c_smbus_read_byte_data(new_client, 0x3e) == 0) && 285*8d5d45fbSJean Delvare (i2c_smbus_read_byte_data(new_client, 0x3f) == 0) && 286*8d5d45fbSJean Delvare (i2c_smbus_read_byte_data(new_client, 0xfe) == 0) && 287*8d5d45fbSJean Delvare (i2c_smbus_read_byte_data(new_client, 0xff) == 0) )) { 288*8d5d45fbSJean Delvare 289*8d5d45fbSJean Delvare /* No vendor ID, now checking if registers 0x10,0x11 (non-existent) 290*8d5d45fbSJean Delvare * showing the same as register 0x00 */ 291*8d5d45fbSJean Delvare temp = i2c_smbus_read_byte_data(new_client, 0x00); 292*8d5d45fbSJean Delvare 293*8d5d45fbSJean Delvare if (!((i2c_smbus_read_byte_data(new_client, 0x10) == temp) && 294*8d5d45fbSJean Delvare (i2c_smbus_read_byte_data(new_client, 0x11) == temp) )) 295*8d5d45fbSJean Delvare goto exit_free; 296*8d5d45fbSJean Delvare } 297*8d5d45fbSJean Delvare 298*8d5d45fbSJean Delvare /* Get VRM */ 299*8d5d45fbSJean Delvare data->vrm = i2c_which_vrm(); 300*8d5d45fbSJean Delvare 301*8d5d45fbSJean Delvare if ((data->vrm != 90) && (data->vrm != 91)) { 302*8d5d45fbSJean Delvare dev_err(&new_client->dev, "Not supporting VRM %d.%d\n", 303*8d5d45fbSJean Delvare data->vrm / 10, data->vrm % 10); 304*8d5d45fbSJean Delvare goto exit_free; 305*8d5d45fbSJean Delvare } 306*8d5d45fbSJean Delvare 307*8d5d45fbSJean Delvare strncpy(new_client->name, "atxp1", I2C_NAME_SIZE); 308*8d5d45fbSJean Delvare 309*8d5d45fbSJean Delvare data->valid = 0; 310*8d5d45fbSJean Delvare 311*8d5d45fbSJean Delvare init_MUTEX(&data->update_lock); 312*8d5d45fbSJean Delvare 313*8d5d45fbSJean Delvare err = i2c_attach_client(new_client); 314*8d5d45fbSJean Delvare 315*8d5d45fbSJean Delvare if (err) 316*8d5d45fbSJean Delvare { 317*8d5d45fbSJean Delvare dev_err(&new_client->dev, "Attach client error.\n"); 318*8d5d45fbSJean Delvare goto exit_free; 319*8d5d45fbSJean Delvare } 320*8d5d45fbSJean Delvare 321*8d5d45fbSJean Delvare device_create_file(&new_client->dev, &dev_attr_gpio1); 322*8d5d45fbSJean Delvare device_create_file(&new_client->dev, &dev_attr_gpio2); 323*8d5d45fbSJean Delvare device_create_file(&new_client->dev, &dev_attr_cpu0_vid); 324*8d5d45fbSJean Delvare 325*8d5d45fbSJean Delvare dev_info(&new_client->dev, "Using VRM: %d.%d\n", 326*8d5d45fbSJean Delvare data->vrm / 10, data->vrm % 10); 327*8d5d45fbSJean Delvare 328*8d5d45fbSJean Delvare return 0; 329*8d5d45fbSJean Delvare 330*8d5d45fbSJean Delvare exit_free: 331*8d5d45fbSJean Delvare kfree(data); 332*8d5d45fbSJean Delvare exit: 333*8d5d45fbSJean Delvare return err; 334*8d5d45fbSJean Delvare }; 335*8d5d45fbSJean Delvare 336*8d5d45fbSJean Delvare static int atxp1_detach_client(struct i2c_client * client) 337*8d5d45fbSJean Delvare { 338*8d5d45fbSJean Delvare int err; 339*8d5d45fbSJean Delvare 340*8d5d45fbSJean Delvare err = i2c_detach_client(client); 341*8d5d45fbSJean Delvare 342*8d5d45fbSJean Delvare if (err) 343*8d5d45fbSJean Delvare dev_err(&client->dev, "Failed to detach client.\n"); 344*8d5d45fbSJean Delvare else 345*8d5d45fbSJean Delvare kfree(i2c_get_clientdata(client)); 346*8d5d45fbSJean Delvare 347*8d5d45fbSJean Delvare return err; 348*8d5d45fbSJean Delvare }; 349*8d5d45fbSJean Delvare 350*8d5d45fbSJean Delvare static int __init atxp1_init(void) 351*8d5d45fbSJean Delvare { 352*8d5d45fbSJean Delvare return i2c_add_driver(&atxp1_driver); 353*8d5d45fbSJean Delvare }; 354*8d5d45fbSJean Delvare 355*8d5d45fbSJean Delvare static void __exit atxp1_exit(void) 356*8d5d45fbSJean Delvare { 357*8d5d45fbSJean Delvare i2c_del_driver(&atxp1_driver); 358*8d5d45fbSJean Delvare }; 359*8d5d45fbSJean Delvare 360*8d5d45fbSJean Delvare module_init(atxp1_init); 361*8d5d45fbSJean Delvare module_exit(atxp1_exit); 362