/* * ms200i_cpld.c - driver for MidStone's CPLD * * Copyright (C) 2017 Celestica Corp. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DRIVER_NAME "ms200i_cpld" #define RESET0102 0xA248 #define RESET0310 0xA2DC #define RESET1118 0xA2DD #define RESET1926 0xA2DE #define RESET2733 0xA2DF #define RESET3441 0xA31C #define RESET4249 0xA31D #define RESET5057 0xA31E #define RESET5864 0xA31F #define LPMOD0102 0xA249 #define LPMOD0310 0xA2E0 #define LPMOD1118 0xA2E1 #define LPMOD1926 0xA2E2 #define LPMOD2733 0xA2E3 #define LPMOD3441 0xA320 #define LPMOD4249 0xA321 #define LPMOD5057 0xA322 #define LPMOD5864 0xA323 #define ABS0102 0xA24A #define ABS0310 0xA2E4 #define ABS1118 0xA2E5 #define ABS1926 0xA2E6 #define ABS2733 0xA2E7 #define ABS3441 0xA324 #define ABS4249 0xA325 #define ABS5057 0xA326 #define ABS5864 0xA327 #define ABS6566 0xA244 #define INT0102 0xA24B #define INT0310 0xA2E8 #define INT1118 0xA2E9 #define INT1926 0xA2EA #define INT2733 0xA2EB #define INT3441 0xA328 #define INT4249 0xA329 #define INT5057 0xA32A #define INT5864 0xA32B #define LENGTH_PORT_CPLD 66 #define CPLD2_EX_CP_I2CFDR0_I2C 0xA230 #define CPLD2_EX_CP_I2CCR0_I2C 0xA231 #define CPLD2_EX_CP_I2CSR0_I2C 0xA232 #define CPLD2_EX_CP_I2CDR0_I2C 0xA233 #define CPLD2_EX_CP_I2CID0_I2C 0xA234 #define CPLD3_EX_CP_I2CFDR0_I2C 0xA2D0 #define CPLD3_EX_CP_I2CCR0_I2C 0xA2D1 #define CPLD3_EX_CP_I2CSR0_I2C 0xA2D2 #define CPLD3_EX_CP_I2CDR0_I2C 0xA2D3 #define CPLD3_EX_CP_I2CID0_I2C 0xA2D4 #define CPLD4_EX_CP_I2CFDR0_I2C 0xA310 #define CPLD4_EX_CP_I2CCR0_I2C 0xA311 #define CPLD4_EX_CP_I2CSR0_I2C 0xA312 #define CPLD4_EX_CP_I2CDR0_I2C 0xA313 #define CPLD4_EX_CP_I2CID0_I2C 0xA314 enum { I2C_SR_BIT_RXAK = 0, I2C_SR_BIT_MIF, I2C_SR_BIT_SRW, I2C_SR_BIT_BCSTM, I2C_SR_BIT_MAL, I2C_SR_BIT_MBB, I2C_SR_BIT_MAAS, I2C_SR_BIT_MCF }; enum { I2C_CR_BIT_BCST = 0, I2C_CR_BIT_RSTA = 2, I2C_CR_BIT_TXAK, I2C_CR_BIT_MTX, I2C_CR_BIT_MSTA, I2C_CR_BIT_MIEN, I2C_CR_BIT_MEN, }; #ifdef DEBUG_KERN #define info(fmt,args...) printk(KERN_INFO "line %3d : "fmt,__LINE__,##args) #define check(REG) printk(KERN_INFO "line %3d : %-8s = %2.2X",__LINE__,#REG,inb(REG)); #else #define info(fmt,args...) #define check(REG) #endif #define GET_REG_BIT(REG,BIT) ((inb(REG) >> BIT) & 0x01) #define SET_REG_BIT_H(REG,BIT) outb(inb(REG) | (0x01 << BIT),REG) #define SET_REG_BIT_L(REG,BIT) outb(inb(REG) & ~(0x01 << BIT),REG) struct ms200i_i2c_data { int portid; unsigned REG_FDR0; unsigned REG_CR0; unsigned REG_SR0; unsigned REG_DR0; unsigned REG_ID0; }; struct ms200i_cpld_data { struct i2c_adapter *i2c_adapter[LENGTH_PORT_CPLD]; struct mutex cpld_lock; unsigned char sfpp_lpmode[2]; unsigned char sfpp_reset[2]; }; struct ms200i_cpld_data *cpld_data; int strtobp(const char* str,unsigned char *bytes){ unsigned length = strlen(str); if(length > 20){ return 0; } int i,b=0; memset(bytes,0,10); for(i=0;i= '0' && c <= '9'){ byte = c - '0'; }else if(c >= 'a' && c <= 'f'){ byte = c - 'a' + 0x0a; }else if(c >= 'A' && c <= 'F'){ byte = c - 'A' + 0x0a; }else if(c == 'x' || c == 'X'){ break; }else{ continue; } if(b%2==0) bytes[b/2] = byte & 0x0F; else bytes[b/2] += (byte << 4) & 0xF0; b++; } return (i/2) + (i%2); } static ssize_t get_reset(struct device *dev, struct device_attribute *devattr, char *buf) { unsigned long reset = 0; mutex_lock(&cpld_data->cpld_lock); reset = ((unsigned long)(inb(RESET5864) & 0x7F) << 57 )| ((unsigned long) inb(RESET5057) << 49 )| ((unsigned long) inb(RESET4249) << 41 )| ((unsigned long) inb(RESET3441) << 33 )| ((unsigned long)(inb(RESET2733) & 0x7F) << 26 )| ((unsigned long) inb(RESET1926) << 18 )| ((unsigned long) inb(RESET1118) << 10 )| ((unsigned long) inb(RESET0310) << 2 )| ((unsigned long)(inb(RESET0102) & 0x03) ); mutex_unlock(&cpld_data->cpld_lock); return sprintf(buf,"0x%x%16.16lx\n", cpld_data->sfpp_reset[1] << 1 | cpld_data->sfpp_reset[0], reset & 0x3ffffffffffffffff); } static ssize_t set_reset(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { unsigned char reset[10]; int num; mutex_lock(&cpld_data->cpld_lock); num = strtobp(buf,reset); if (num <= 0) { mutex_unlock(&cpld_data->cpld_lock); return 22; } outb (( reset[0] & 0x03 ) ,RESET0102); outb (((reset[0] >>2) & 0x3F) | (((reset[1] ) & 0x03) << 6),RESET0310); outb (((reset[1] >>2) & 0x3F) | (((reset[2] ) & 0x03) << 6),RESET1118); outb (((reset[2] >>2) & 0x3F) | (((reset[3] ) & 0x03) << 6),RESET1926); outb (((reset[3] >>2) & 0x3F) | (((reset[4] ) & 0x01) << 6),RESET2733); outb (((reset[4] >>1) & 0x7F) | (((reset[5] ) & 0x01) << 7),RESET3441); outb (((reset[5] >>1) & 0x7F) | (((reset[6] ) & 0x01) << 7),RESET4249); outb (((reset[6] >>1) & 0x7F) | (((reset[7] ) & 0x01) << 7),RESET5057); outb (((reset[7] >>1) & 0x7F) ,RESET5864); cpld_data->sfpp_reset[0] = reset[8] & 0x01; cpld_data->sfpp_reset[1] = (reset[8]>>1) & 0x01; mutex_unlock(&cpld_data->cpld_lock); return count; } static ssize_t get_lpmode(struct device *dev, struct device_attribute *devattr, char *buf) { unsigned long lpmod = 0; mutex_lock(&cpld_data->cpld_lock); lpmod = ((unsigned long)(inb(LPMOD5864) & 0x7F) << 57 )| ((unsigned long) inb(LPMOD5057) << 49 )| ((unsigned long) inb(LPMOD4249) << 41 )| ((unsigned long) inb(LPMOD3441) << 33 )| ((unsigned long)(inb(LPMOD2733) & 0x7F) << 26 )| ((unsigned long) inb(LPMOD1926) << 18 )| ((unsigned long) inb(LPMOD1118) << 10 )| ((unsigned long) inb(LPMOD0310) << 2 )| ((unsigned long)(inb(LPMOD0102) & 0x03) ); mutex_unlock(&cpld_data->cpld_lock); return sprintf(buf,"0x%x%16.16lx\n", cpld_data->sfpp_lpmode[1] << 1 | cpld_data->sfpp_lpmode[0], lpmod & 0xffffffffffffffff); } static ssize_t set_lpmode(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { unsigned char lpmod[10]; int num; mutex_lock(&cpld_data->cpld_lock); num = strtobp(buf,lpmod); if (num <= 0) { mutex_unlock(&cpld_data->cpld_lock); return 22; } outb (( lpmod[0] & 0x03 ) ,LPMOD0102); outb (((lpmod[0] >>2) & 0x3F) | (((lpmod[1] ) & 0x03) << 6),LPMOD0310); outb (((lpmod[1] >>2) & 0x3F) | (((lpmod[2] ) & 0x03) << 6),LPMOD1118); outb (((lpmod[2] >>2) & 0x3F) | (((lpmod[3] ) & 0x03) << 6),LPMOD1926); outb (((lpmod[3] >>2) & 0x3F) | (((lpmod[4] ) & 0x01) << 6),LPMOD2733); outb (((lpmod[4] >>1) & 0x7F) | (((lpmod[5] ) & 0x01) << 7),LPMOD3441); outb (((lpmod[5] >>1) & 0x7F) | (((lpmod[6] ) & 0x01) << 7),LPMOD4249); outb (((lpmod[6] >>1) & 0x7F) | (((lpmod[7] ) & 0x01) << 7),LPMOD5057); outb (((lpmod[7] >>1) & 0x7F) ,LPMOD5864); cpld_data->sfpp_lpmode[0] = lpmod[8] & 0x01; cpld_data->sfpp_lpmode[1] = (lpmod[8]>>1) & 0x01; mutex_unlock(&cpld_data->cpld_lock); return count; } static ssize_t get_modprs(struct device *dev, struct device_attribute *devattr, char *buf) { unsigned long present; int present_sfp; mutex_lock(&cpld_data->cpld_lock); present = ((unsigned long)(inb(ABS5864) & 0x7F) << 57 )| ((unsigned long) inb(ABS5057) << 49 )| ((unsigned long) inb(ABS4249) << 41 )| ((unsigned long) inb(ABS3441) << 33 )| ((unsigned long)(inb(ABS2733) & 0x7F) << 26 )| ((unsigned long) inb(ABS1926) << 18 )| ((unsigned long) inb(ABS1118) << 10 )| ((unsigned long) inb(ABS0310) << 2 )| ((unsigned long)(inb(ABS0102) & 0x03) ); present_sfp = (inb(ABS6566) & 0x03); mutex_unlock(&cpld_data->cpld_lock); return sprintf(buf,"0x%d%16.16lx\n", present_sfp, present & 0xffffffffffffffff); } static ssize_t get_modirq(struct device *dev, struct device_attribute *devattr, char *buf) { unsigned long irq; mutex_lock(&cpld_data->cpld_lock); irq = ((unsigned long)(inb(INT5864) & 0x7F) << 57 )| ((unsigned long) inb(INT5057) << 49 )| ((unsigned long) inb(INT4249) << 41 )| ((unsigned long) inb(INT3441) << 33 )| ((unsigned long)(inb(INT2733) & 0x7F) << 26 )| ((unsigned long) inb(INT1926) << 18 )| ((unsigned long) inb(INT1118) << 10 )| ((unsigned long) inb(INT0310) << 2 )| ((unsigned long)(inb(INT0102) & 0x03) ); mutex_unlock(&cpld_data->cpld_lock); return sprintf(buf,"0x%17.17lx\n", irq & 0x3ffffffffffffffff); } static DEVICE_ATTR(qsfp_reset , S_IRUGO | S_IWUSR, get_reset, set_reset); static DEVICE_ATTR(qsfp_lpmode, S_IRUGO | S_IWUSR, get_lpmode, set_lpmode); static DEVICE_ATTR(qsfp_modprs, S_IRUGO, get_modprs, NULL); static DEVICE_ATTR(qsfp_modirq, S_IRUGO, get_modirq, NULL); static struct attribute *ms200i_lpc_attrs[] = { &dev_attr_qsfp_reset.attr, &dev_attr_qsfp_lpmode.attr, &dev_attr_qsfp_modprs.attr, &dev_attr_qsfp_modirq.attr, NULL, }; static struct attribute_group ms200i_lpc_attr_grp = { .attrs = ms200i_lpc_attrs, }; static struct resource cel_ms200i_lpc_resources[] = { { .flags = IORESOURCE_IO, }, }; static void cel_ms200i_lpc_dev_release( struct device * dev) { return; } static struct platform_device cel_ms200i_lpc_dev = { .name = DRIVER_NAME, .id = -1, .num_resources = ARRAY_SIZE(cel_ms200i_lpc_resources), .resource = cel_ms200i_lpc_resources, .dev = { .release = cel_ms200i_lpc_dev_release, } }; static int i2c_wait_ack(struct i2c_adapter *a,unsigned timeout,int writing){ int error = 0; unsigned tick=0; int Status; struct ms200i_i2c_data *new_data = i2c_get_adapdata(a); check(new_data->REG_SR0); check(new_data->REG_CR0); while(1){ Status = inb(new_data->REG_SR0); tick++; if(tick > timeout){ info("Status %2.2X",Status); info("Error Timeout"); error = -ETIMEDOUT; break; } if(Status & (1 << I2C_SR_BIT_MIF)){ break; } if(writing == 0 && (Status & (1<REG_SR0); outb(0, new_data->REG_SR0); if(error<0){ info("Status %2.2X",Status); return error; } if(!(Status & (1 << I2C_SR_BIT_MCF))){ info("Error Unfinish"); return -EIO; } if(Status & (1 <REG_CR0,1<cpld_lock); /* Write the command register */ new_data = i2c_get_adapdata(a); unsigned int portid = new_data->portid; #ifdef DEBUG_KERN printk(KERN_INFO "portid %2d|@ 0x%2.2X|f 0x%4.4X|(%d)%-5s| (%d)%-10s|CMD %2.2X |DAT %4.4X" ,portid,addr,flags,rw,rw == 1 ? "READ ":"WRITE" ,size, size == 0 ? "QUICK" : size == 1 ? "BYTE" : size == 2 ? "BYTE_DATA" : size == 3 ? "WORD_DATA" : size == 4 ? "PROC_CALL" : size == 5 ? "BLOCK_DATA" : "ERROR" ,cmd,data->word); #endif /* Map the size to what the chip understands */ switch (size) { case I2C_SMBUS_QUICK: case I2C_SMBUS_BYTE: case I2C_SMBUS_BYTE_DATA: case I2C_SMBUS_WORD_DATA: case I2C_SMBUS_BLOCK_DATA: break; default: printk(KERN_INFO "Unsupported transaction %d\n", size); error = -EOPNOTSUPP; goto Done; } unsigned int REG_FDR0; unsigned int REG_CR0; unsigned int REG_SR0; unsigned int REG_DR0; unsigned int REG_ID0; REG_FDR0 = new_data->REG_FDR0; REG_CR0 = new_data->REG_CR0; REG_SR0 = new_data->REG_SR0; REG_DR0 = new_data->REG_DR0; REG_ID0 = new_data->REG_ID0; outb(portid,REG_ID0); int timeout=0; int cnt=0; ////[S][ADDR/R] //Clear status register outb( 0 , REG_SR0); outb( 1 << I2C_CR_BIT_MIEN | 1 << I2C_CR_BIT_MTX | 1 << I2C_CR_BIT_MSTA ,REG_CR0); SET_REG_BIT_H(REG_CR0,I2C_CR_BIT_MEN); if(rw == I2C_SMBUS_READ && (size == I2C_SMBUS_QUICK || size == I2C_SMBUS_BYTE)){ // sent device address with Read mode outb(addr << 1 | 0x01,REG_DR0); }else{ // sent device address with Write mode outb(addr << 1 | 0x00,REG_DR0); } info( "MS Start"); //// Wait {A} error = i2c_wait_ack(a,50000,1); if(error<0){ info( "get error %d",error); goto Done; } //// [CMD]{A} if(size == I2C_SMBUS_BYTE_DATA || size == I2C_SMBUS_WORD_DATA || size == I2C_SMBUS_BLOCK_DATA || (size == I2C_SMBUS_BYTE && rw == I2C_SMBUS_WRITE)){ //sent command code to data register outb(cmd,REG_DR0); info( "MS Send CMD 0x%2.2X",cmd); // Wait {A} error = i2c_wait_ack(a,50000,1); if(error<0){ info( "get error %d",error); goto Done; } } switch(size){ case I2C_SMBUS_BYTE_DATA: cnt = 1; break; case I2C_SMBUS_WORD_DATA: cnt = 2; break; case I2C_SMBUS_BLOCK_DATA: // in block data mode keep number of byte in block[0] cnt = data->block[0]; break; default: cnt = 0; break; } // [CNT] used only bloack data write if(size == I2C_SMBUS_BLOCK_DATA && rw == I2C_SMBUS_WRITE){ outb(cnt,REG_DR0); info( "MS Send CNT 0x%2.2X",cnt); // Wait {A} error = i2c_wait_ack(a,50000,1); if(error<0){ info( "get error %d",error); goto Done; } } // [DATA]{A} if( rw == I2C_SMBUS_WRITE && ( size == I2C_SMBUS_BYTE || size == I2C_SMBUS_BYTE_DATA || size == I2C_SMBUS_WORD_DATA || size == I2C_SMBUS_BLOCK_DATA )){ int bid=0; info( "MS prepare to sent [%d bytes]",cnt); if(size == I2C_SMBUS_BLOCK_DATA ){ bid=1; // block[0] is cnt; cnt+=1; // offset from block[0] } for(;bidblock[bid],REG_DR0); info( " Data > %2.2X",data->block[bid]); // Wait {A} error = i2c_wait_ack(a,50000,1); if(error<0){ goto Done; } } } //REPEATE START if( rw == I2C_SMBUS_READ && ( size == I2C_SMBUS_BYTE_DATA || size == I2C_SMBUS_WORD_DATA || size == I2C_SMBUS_BLOCK_DATA )){ info( "MS Repeated Start"); SET_REG_BIT_L(REG_CR0,I2C_CR_BIT_MEN); outb(1 << I2C_CR_BIT_MIEN | 1 << I2C_CR_BIT_MTX | 1 << I2C_CR_BIT_MSTA | 1 << I2C_CR_BIT_RSTA ,REG_CR0); SET_REG_BIT_H(REG_CR0,I2C_CR_BIT_MEN); // sent Address with Read mode outb( addr<<1 | 0x1 ,REG_DR0); // Wait {A} error = i2c_wait_ack(a,50000,1); if(error<0){ goto Done; } } if( rw == I2C_SMBUS_READ && ( size == I2C_SMBUS_BYTE || size == I2C_SMBUS_BYTE_DATA || size == I2C_SMBUS_WORD_DATA || size == I2C_SMBUS_BLOCK_DATA )){ switch(size){ case I2C_SMBUS_BYTE: case I2C_SMBUS_BYTE_DATA: cnt = 1; break; case I2C_SMBUS_WORD_DATA: cnt = 2; break; case I2C_SMBUS_BLOCK_DATA: //will be changed after recived first data cnt = 3; break; default: cnt = 0; break; } int bid = 0; info( "MS Receive"); //set to Receive mode outb(1 << I2C_CR_BIT_MEN | 1 << I2C_CR_BIT_MIEN | 1 << I2C_CR_BIT_MSTA , REG_CR0); for(bid=-1;bidblock[bid] = inb(REG_DR0); info( "DATA IN [%d] %2.2X",bid,data->block[bid]); if(size==I2C_SMBUS_BLOCK_DATA && bid == 0){ cnt = data->block[0] + 1; } } } } Stop: //[P] SET_REG_BIT_L(REG_CR0,I2C_CR_BIT_MSTA); info( "MS STOP"); Done: outb(1<cpld_lock); return error; } static u32 ms200i_i2c_func(struct i2c_adapter *a) { return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BLOCK_DATA; } static const struct i2c_algorithm ms200i_i2c_algorithm = { .smbus_xfer = ms200i_i2c_access, .functionality = ms200i_i2c_func, }; static struct i2c_adapter * cel_ms200i_i2c_init(struct platform_device *pdev, int portid) { int error; struct i2c_adapter *new_adapter; struct ms200i_i2c_data *new_data; new_adapter = kzalloc(sizeof(*new_adapter), GFP_KERNEL); if (!new_adapter) return NULL; new_adapter->dev.parent = &pdev->dev; new_adapter->owner = THIS_MODULE; new_adapter->class = I2C_CLASS_HWMON | I2C_CLASS_SPD; new_adapter->algo = &ms200i_i2c_algorithm; snprintf(new_adapter->name, sizeof(new_adapter->name), "SMBus ms200i i2c Adapter portid@%04x", portid); new_data = kzalloc(sizeof(*new_data), GFP_KERNEL); if (!new_data) return NULL; new_data->portid = portid; // QSFP 1-2 and SFP 1-2 if((portid >= 1 && portid <= 2) || (portid >= 65 && portid <= 66)){ new_data->REG_FDR0 = CPLD2_EX_CP_I2CFDR0_I2C; new_data->REG_CR0 = CPLD2_EX_CP_I2CCR0_I2C; new_data->REG_SR0 = CPLD2_EX_CP_I2CSR0_I2C; new_data->REG_DR0 = CPLD2_EX_CP_I2CDR0_I2C; new_data->REG_ID0 = CPLD2_EX_CP_I2CID0_I2C; }else if((portid >= 3 && portid <= 33)){ new_data->REG_FDR0 = CPLD3_EX_CP_I2CFDR0_I2C; new_data->REG_CR0 = CPLD3_EX_CP_I2CCR0_I2C; new_data->REG_SR0 = CPLD3_EX_CP_I2CSR0_I2C; new_data->REG_DR0 = CPLD3_EX_CP_I2CDR0_I2C; new_data->REG_ID0 = CPLD3_EX_CP_I2CID0_I2C; }else if((portid >= 34 && portid <= 64)){ new_data->REG_FDR0 = CPLD4_EX_CP_I2CFDR0_I2C; new_data->REG_CR0 = CPLD4_EX_CP_I2CCR0_I2C; new_data->REG_SR0 = CPLD4_EX_CP_I2CSR0_I2C; new_data->REG_DR0 = CPLD4_EX_CP_I2CDR0_I2C; new_data->REG_ID0 = CPLD4_EX_CP_I2CID0_I2C; } outb(portid,new_data->REG_ID0); outb(0x1F,new_data->REG_FDR0); // 0x1F 100kHz i2c_set_adapdata(new_adapter,new_data); error = i2c_add_adapter(new_adapter); if(error) return NULL; return new_adapter; }; static int cel_ms200i_lpc_drv_probe(struct platform_device *pdev) { struct resource *res; int ret =0; int portid_count; cpld_data = devm_kzalloc(&pdev->dev, sizeof(struct ms200i_cpld_data), GFP_KERNEL); if (!cpld_data) return -ENOMEM; mutex_init(&cpld_data->cpld_lock); res = platform_get_resource(pdev, IORESOURCE_IO, 0); if (unlikely(!res)) { printk(KERN_ERR " Specified Resource Not Available...\n"); return -1; } ret = sysfs_create_group(&pdev->dev.kobj, &ms200i_lpc_attr_grp); if (ret) { printk(KERN_ERR "Cannot create sysfs\n"); } for(portid_count=1 ; portid_count<=LENGTH_PORT_CPLD ; portid_count++) cpld_data->i2c_adapter[portid_count-1] = cel_ms200i_i2c_init(pdev, portid_count); return 0; } static int cel_ms200i_lpc_drv_remove(struct platform_device *pdev) { int portid_count; struct ms200i_i2c_data *new_data; sysfs_remove_group(&pdev->dev.kobj, &ms200i_lpc_attr_grp); for (portid_count=1 ; portid_count<=LENGTH_PORT_CPLD ; portid_count++) i2c_del_adapter(cpld_data->i2c_adapter[portid_count-1]); return 0; } static struct platform_driver cel_ms200i_lpc_drv = { .probe = cel_ms200i_lpc_drv_probe, .remove = __exit_p(cel_ms200i_lpc_drv_remove), .driver = { .name = DRIVER_NAME, }, }; int cel_ms200i_lpc_init(void) { platform_device_register(&cel_ms200i_lpc_dev); platform_driver_register(&cel_ms200i_lpc_drv); return 0; } void cel_ms200i_lpc_exit(void) { platform_driver_unregister(&cel_ms200i_lpc_drv); platform_device_unregister(&cel_ms200i_lpc_dev); } module_init(cel_ms200i_lpc_init); module_exit(cel_ms200i_lpc_exit); MODULE_AUTHOR("Pariwat Leamsumran "); MODULE_DESCRIPTION("Celestica MidStone ms200i LPC Driver"); MODULE_LICENSE("GPL");