/* * Copyright (c) 2015 South Silicon Valley Microelectronics Inc. * Copyright (c) 2015 iComm Corporation * * 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 3 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sdiobridge.h" #include "debug.h" #define BLOCKSIZE 0x40 #define RXBUFLENGTH 1024*3 #define RXBUFSIZE 512 enum ssvcabrio_int { SSVCABRIO_INT_RX = 0x00000001, SSVCABRIO_INT_TX = 0x00000002, SSVCABRIO_INT_GPIO = 0x00000004, SSVCABRIO_INT_SYS = 0x00000008, }; #define CHECK_RET(_fun) \ do { \ if (0 != _fun) \ printk("File = %s\nLine = %d\nFunc=%s\nDate=%s\nTime=%s\n", __FILE__, __LINE__, __FUNCTION__, __DATE__, __TIME__); \ } while (0) static unsigned int ssv_sdiobridge_ioctl_major = 0; static unsigned int num_of_dev = 1; static struct cdev ssv_sdiobridge_ioctl_cdev; static struct class *fc; static struct ssv_sdiobridge_glue *glue; struct ssv_rxbuf { struct list_head list; u32 rxsize; u8 rxdata[RXBUFLENGTH]; }; static const struct sdio_device_id ssv_sdiobridge_devices[] = { {SDIO_DEVICE(MANUFACTURER_SSV_CODE, (MANUFACTURER_ID_CABRIO_BASE | 0x0))}, {} }; MODULE_DEVICE_TABLE(sdio, ssv_sdiobridge_devices); static long ssv_sdiobridge_ioctl_getFuncfocus(struct ssv_sdiobridge_glue *glue,unsigned int cmd, struct ssv_sdiobridge_cmd *pcmd_data,struct ssv_sdiobridge_cmd *pucmd_data,bool isCompat) { long retval =0; if ( pcmd_data->out_data_len < 1) { retval = -1; } else { u8 out_data = glue->funcFocus; if ( isCompat ) { CHECK_RET(copy_to_user((int __user *)compat_ptr((unsigned long)pucmd_data->out_data),&out_data,sizeof(out_data))); } else { CHECK_RET(copy_to_user((int __user *)pucmd_data->out_data, &out_data, sizeof(out_data))); } } return retval; } static long ssv_sdiobridge_ioctl_setFuncfocus(struct ssv_sdiobridge_glue *glue,unsigned int cmd, struct ssv_sdiobridge_cmd *pcmd_data,struct ssv_sdiobridge_cmd *pucmd_data,bool isCompat) { int retval =0; if ( pcmd_data->out_data_len < 0) { retval = -EFAULT; dev_err(glue->dev, "%s : input length must < 0",__FUNCTION__); } else { if ( isCompat ) { CHECK_RET(copy_from_user(&glue->funcFocus,(int __user *)compat_ptr((unsigned long)pcmd_data->in_data),sizeof(glue->funcFocus))); } else { CHECK_RET(copy_from_user(&glue->funcFocus,(int __user *)pcmd_data->in_data,sizeof(glue->funcFocus))); } } return retval; } static long ssv_sdiobridge_ioctl_getBusWidth(struct ssv_sdiobridge_glue *glue,unsigned int cmd, struct ssv_sdiobridge_cmd *pcmd_data,struct ssv_sdiobridge_cmd *pucmd_data,bool isCompat) { struct sdio_func *func = dev_to_sdio_func(glue->dev); long retval =0; if ( pcmd_data->out_data_len < 1) { retval = -1; } else { u8 out_data = 1; if ( func->card->host->ios.bus_width != MMC_BUS_WIDTH_1 ) { out_data = 4; } if ( isCompat ) { CHECK_RET(copy_to_user((int __user *)compat_ptr((unsigned long)pucmd_data->out_data),&out_data,sizeof(out_data))); } else { CHECK_RET(copy_to_user((int __user *)pucmd_data->out_data,&out_data,sizeof(out_data))); } } return retval; } #if 0 static long ssv_sdiobridge_ioctl_setBusWidth(struct ssv_sdiobridge_glue *glue,unsigned int cmd, struct ssv_sdiobridge_cmd *pcmd_data,struct ssv_sdiobridge_cmd *pucmd_data,bool isCompat) { struct sdio_func *func = dev_to_sdio_func(glue->dev); int retval =0; struct ssv_sdiobridge_cmd *pData; u8 inData[1]; copy_from_user(pData,(int __user *)arg,sizeof(*pData)); if ( isCompat ) { copy_from_user(&cmd_data,(int __user *)compat_ptr((unsigned long)pucmd_data),sizeof(*pucmd_data)); } else { copy_from_user(&cmd_data,(int __user *)pucmd_data,sizeof(*pucmd_data)); } if ( pData->in_data_len < 0) { retval = -EFAULT; dev_err(glue->dev, "%s : input length must > 1",__FUNCTION__); } else { if ( pData->in_data == 1 ) { if ( (func->card->host->caps & MMC_CAP_4_BIT_DATA) && !(func->card->cccr.low_speed && !func->card->cccr.wide_bus) ) { extern void mmc_set_bus_width(struct mmc_host *host, unsigned int width); u8 ctrl = sdio_f0_readb(func,SDIO_CCCR_IF,&retval); if (retval) return retval; if (!(ctrl & SDIO_BUS_WIDTH_4BIT)) return 0; ctrl &= ~SDIO_BUS_WIDTH_4BIT; ctrl |= SDIO_BUS_ASYNC_INT; sdio_f0_writeb(func,ctrl,SDIO_CCCR_IF,&retval); if (retval) return retval; mmc_set_bus_width(func->card->host, MMC_BUS_WIDTH_1); } } else { if ( func->card->host->ios.bus_width != MMC_BUS_WIDTH_4 ) { } } } return retval; } static long ssv_sdiobridge_ioctl_getBlockMode(struct ssv_sdiobridge_glue *glue,unsigned int cmd, struct ssv_sdiobridge_cmd *pcmd_data,struct ssv_sdiobridge_cmd *pucmd_data,bool isCompat) { long retval =0; if ( pcmd_data->out_data_len < 1) { retval = -1; } else { if ( isCompat ) { copy_to_user((int __user *)compat_ptr((unsigned long)pucmd_data->out_data),&out_data,sizeof(out_data)); } else { copy_to_user((int __user *)pucmd_data->out_data,&out_data,sizeof(out_data)); } } return retval; } static long ssv_sdiobridge_ioctl_setBlockMode(struct ssv_sdiobridge_glue *glue,unsigned int cmd, struct ssv_sdiobridge_cmd *pcmd_data,struct ssv_sdiobridge_cmd *pucmd_data,bool isCompat) { long retval =0; if ( pcmd_data->in_data_len < 1) { retval = -1; } else { if ( isCompat ) { copy_from_user(&glue->blockMode,(int __user *)pucmd_data->in_data,sizeof(glue->blockMode)); } } return retval; } #endif static long ssv_sdiobridge_ioctl_getBlockSize(struct ssv_sdiobridge_glue *glue,unsigned int cmd, struct ssv_sdiobridge_cmd *pcmd_data,struct ssv_sdiobridge_cmd *pucmd_data,bool isCompat) { long retval =0; if ( pcmd_data->out_data_len < 2) { retval = -1; } else { if ( isCompat ) { CHECK_RET(copy_to_user((int __user *)compat_ptr((unsigned long)pucmd_data->out_data),&glue->blockSize,sizeof(glue->blockSize))); } else { CHECK_RET(copy_to_user((int __user *)pucmd_data->out_data,&glue->blockSize,sizeof(glue->blockSize))); } } return retval; } static long ssv_sdiobridge_ioctl_setBlockSize(struct ssv_sdiobridge_glue *glue,unsigned int cmd, struct ssv_sdiobridge_cmd *pcmd_data,struct ssv_sdiobridge_cmd *pucmd_data,bool isCompat) { struct sdio_func *func = dev_to_sdio_func(glue->dev); long retval =0; if ( pcmd_data->in_data_len < 2) { retval = -1; } else { if ( isCompat ) { CHECK_RET(copy_from_user(&glue->blockSize,(int __user *)compat_ptr((unsigned long)pucmd_data->in_data),sizeof(glue->blockSize))); } else { CHECK_RET(copy_from_user(&glue->blockSize,(int __user *)pucmd_data->in_data,sizeof(glue->blockSize))); } dev_err(glue->dev,"%s: blockSize [%d]\n",__FUNCTION__,glue->blockSize); sdio_claim_host(func); sdio_set_block_size(func,glue->blockSize); sdio_release_host(func); } return retval; } static long ssv_sdiobridge_ioctl_readByte(struct ssv_sdiobridge_glue *glue,unsigned int cmd, struct ssv_sdiobridge_cmd *pcmd_data,struct ssv_sdiobridge_cmd *pucmd_data,bool isCompat) { struct sdio_func *func = dev_to_sdio_func(glue->dev); long retval =0; if ( pcmd_data->out_data_len < 4) { retval = -1; } else { u32 address; u8 out_data; int ret = 0; if ( isCompat ) { CHECK_RET(copy_from_user(&address,(int __user *)compat_ptr((unsigned long)pucmd_data->in_data),sizeof(address))); } else { CHECK_RET(copy_from_user(&address,(int __user *)pucmd_data->in_data,sizeof(address))); } sdio_claim_host(func); if ( glue->funcFocus == 0 ) { out_data = sdio_f0_readb(func, address, &ret); } else { out_data = sdio_readb(func, address, &ret); } sdio_release_host(func); dev_err(glue->dev,"%s: [%X] [%02X] ret:[%d]\n",__FUNCTION__,address,out_data,ret); if ( !ret ) { if ( isCompat ) { CHECK_RET(copy_to_user((void *)compat_ptr((unsigned long)pucmd_data->out_data),&out_data,sizeof(out_data))); } else { CHECK_RET(copy_to_user((int __user *)pucmd_data->out_data,&out_data,sizeof(out_data))); } } else { dev_err(glue->dev,"%s: error : %d",__FUNCTION__,ret); retval = -1; } } return retval; } static long ssv_sdiobridge_ioctl_writeByte(struct ssv_sdiobridge_glue *glue,unsigned int cmd, struct ssv_sdiobridge_cmd *pcmd_data,struct ssv_sdiobridge_cmd *pucmd_data,bool isCompat) { struct sdio_func *func = dev_to_sdio_func(glue->dev); long retval =0; if ( pcmd_data->in_data_len < 5) { retval = -1; } else { u8 tmp[5]; u32 address; u8 data; int ret = 0; if ( isCompat ) { CHECK_RET(copy_from_user(tmp,(int __user *)compat_ptr((unsigned long)pucmd_data->in_data),sizeof(tmp))); } else { CHECK_RET(copy_from_user(tmp,(int __user *)pucmd_data->in_data,sizeof(tmp))); } address = *((u32 *)tmp); data = tmp[4]; sdio_claim_host(func); if ( glue->funcFocus == 0 ) { sdio_f0_writeb(func,data, address, &ret); } else { sdio_writeb(func,data, address, &ret); } sdio_release_host(func); if ( ret ) { dev_err(glue->dev,"%s: error : %d",__FUNCTION__,ret); retval = -1; } } return retval; } static long ssv_sdiobridge_ioctl_getMultiByteIOPort(struct ssv_sdiobridge_glue *glue,unsigned int cmd, struct ssv_sdiobridge_cmd *pcmd_data,struct ssv_sdiobridge_cmd *pucmd_data,bool isCompat) { long retval =0; if ( pcmd_data->out_data_len < 4) { retval = -1; } else { if ( isCompat ) { CHECK_RET(copy_to_user((int __user *)compat_ptr((unsigned long)pucmd_data->out_data),&glue->dataIOPort,sizeof(glue->dataIOPort))); } else { CHECK_RET(copy_to_user((int __user *)pucmd_data->out_data,&glue->dataIOPort,sizeof(glue->dataIOPort))); } } return retval; } static long ssv_sdiobridge_ioctl_setMultiByteIOPort(struct ssv_sdiobridge_glue *glue,unsigned int cmd, struct ssv_sdiobridge_cmd *pcmd_data,struct ssv_sdiobridge_cmd *pucmd_data,bool isCompat) { long retval =0; if ( pcmd_data->in_data_len < 4) { retval = -1; } else { if ( isCompat ) { CHECK_RET(copy_from_user(&glue->dataIOPort,(int __user *)compat_ptr((unsigned long)pucmd_data->in_data),sizeof(glue->dataIOPort))); } else { CHECK_RET(copy_from_user(&glue->dataIOPort,(int __user *)pucmd_data->in_data,sizeof(glue->dataIOPort))); } } return retval; } static long ssv_sdiobridge_ioctl_readMultiByte(struct ssv_sdiobridge_glue *glue,unsigned int cmd, struct ssv_sdiobridge_cmd *pcmd_data,struct ssv_sdiobridge_cmd *pucmd_data,bool isCompat) { struct sdio_func *func = dev_to_sdio_func(glue->dev); long retval =0; u8 *tmpdata; int ret; int readsize; tmpdata = kzalloc(pcmd_data->out_data_len, GFP_KERNEL); if ( tmpdata == NULL ) { dev_err(glue->dev,"%s: error : alloc buf error size:%d",__FUNCTION__,pcmd_data->out_data_len); return -1; } readsize = sdio_align_size(func,pcmd_data->out_data_len); sdio_claim_host(func); ret = sdio_memcpy_fromio(func, tmpdata,glue->dataIOPort, readsize ); sdio_release_host(func); if (unlikely(glue->dump)) { printk(KERN_DEBUG "%s: READ data address[%08x] len[%d] readsize[%d]\n",__FUNCTION__,glue->dataIOPort,(int)pcmd_data->out_data_len,readsize); print_hex_dump(KERN_DEBUG, "ssv_sdio: READ ", DUMP_PREFIX_OFFSET, 16, 1, tmpdata, pcmd_data->out_data_len, false); } if ( !ret ) { if ( isCompat ) { CHECK_RET(copy_to_user((int __user *)compat_ptr((unsigned long)pucmd_data->out_data),tmpdata,pcmd_data->out_data_len)); } else { CHECK_RET(copy_to_user((int __user *)pucmd_data->out_data,tmpdata,pcmd_data->out_data_len)); } } else { dev_err(glue->dev,"%s: error : %d",__FUNCTION__,ret); retval = -1; } kfree(tmpdata); dev_err(glue->dev,"%s(): %d\n", __FUNCTION__, ret); return retval; } static long ssv_sdiobridge_ioctl_writeMultiByte(struct ssv_sdiobridge_glue *glue,unsigned int cmd, struct ssv_sdiobridge_cmd *pcmd_data,struct ssv_sdiobridge_cmd *pucmd_data,bool isCompat) { struct sdio_func *func = dev_to_sdio_func(glue->dev); long retval =0; u8 *tmpdata; int ret; int readsize ; tmpdata = kzalloc(pcmd_data->in_data_len, GFP_KERNEL); if ( tmpdata == NULL ) { dev_err(glue->dev,"%s: error : alloc buf error size:%d",__FUNCTION__,pcmd_data->out_data_len); return -1; } if ( isCompat ) { CHECK_RET(copy_from_user(tmpdata,(int __user *)compat_ptr((unsigned long)pucmd_data->in_data),pcmd_data->in_data_len)); } else { CHECK_RET(copy_from_user(tmpdata,(int __user *)pucmd_data->in_data,pcmd_data->in_data_len)); } readsize = sdio_align_size(func,pcmd_data->in_data_len); if (unlikely(glue->dump)) { printk(KERN_DEBUG "%s: READ data address[%08x] len[%d] readsize[%d]\n",__FUNCTION__,glue->dataIOPort,(int)pcmd_data->in_data_len,readsize); print_hex_dump(KERN_DEBUG, "ssv_sdio: WRITE ", DUMP_PREFIX_OFFSET, 16, 1, tmpdata, pcmd_data->in_data_len, false); } sdio_claim_host(func); ret = sdio_memcpy_toio(func, glue->dataIOPort,tmpdata, readsize); sdio_release_host(func); if ( ret ) { dev_err(glue->dev,"%s: error : %d",__FUNCTION__,ret); retval = -1; } kfree(tmpdata); return retval; } static long ssv_sdiobridge_ioctl_getMultiByteRegIOPort(struct ssv_sdiobridge_glue *glue,unsigned int cmd, struct ssv_sdiobridge_cmd *pcmd_data,struct ssv_sdiobridge_cmd *pucmd_data,bool isCompat) { long retval =0; if ( pcmd_data->out_data_len < 4) { retval = -1; } else { if ( isCompat ) { CHECK_RET(copy_to_user((int __user *)compat_ptr((unsigned long)pucmd_data->out_data),&glue->regIOPort,sizeof(glue->regIOPort))); } else { CHECK_RET(copy_to_user((int __user *)pucmd_data->out_data,&glue->regIOPort,sizeof(glue->regIOPort))); } } return retval; } static long ssv_sdiobridge_ioctl_setMultiByteRegIOPort(struct ssv_sdiobridge_glue *glue,unsigned int cmd, struct ssv_sdiobridge_cmd *pcmd_data,struct ssv_sdiobridge_cmd *pucmd_data,bool isCompat) { long retval =0; if ( pcmd_data->in_data_len < 4) { retval = -1; } else { if ( isCompat ) { CHECK_RET(copy_from_user(&glue->regIOPort,(int __user *)compat_ptr((unsigned long)pucmd_data->in_data),sizeof(glue->regIOPort))); } else { CHECK_RET(copy_from_user(&glue->regIOPort,(int __user *)pucmd_data->in_data,sizeof(glue->regIOPort))); } } return retval; } static long ssv_sdiobridge_ioctl_readReg(struct ssv_sdiobridge_glue *glue,unsigned int cmd, struct ssv_sdiobridge_cmd *pcmd_data,struct ssv_sdiobridge_cmd *pucmd_data,bool isCompat) { struct sdio_func *func = dev_to_sdio_func(glue->dev); long retval =0; if ( pcmd_data->in_data_len < 4 || pcmd_data->out_data_len < 4) { retval = -1; } else { u8 tmpdata[4]; int ret = 0; if ( isCompat ) { CHECK_RET(copy_from_user(tmpdata,(int __user *)compat_ptr((unsigned long)pucmd_data->in_data),sizeof(tmpdata))); } else { CHECK_RET(copy_from_user(tmpdata,(int __user *)pucmd_data->in_data,sizeof(tmpdata))); } sdio_claim_host(func); dev_err(glue->dev,"%s: read reg 1 [%02X][%02X][%02X][%02X]\n",__FUNCTION__,tmpdata[0],tmpdata[1],tmpdata[2],tmpdata[3]); ret = sdio_memcpy_toio(func, glue->regIOPort, tmpdata, 4); ret = sdio_memcpy_fromio(func, tmpdata, glue->regIOPort, 4); sdio_release_host(func); dev_err(glue->dev,"%s: read reg 2 [%02X][%02X][%02X][%02X] ret:%d\n",__FUNCTION__,tmpdata[0],tmpdata[1],tmpdata[2],tmpdata[3],ret); if ( !ret ) { if ( isCompat ) { CHECK_RET(copy_to_user((int __user *)compat_ptr((unsigned long)pucmd_data->out_data),tmpdata,sizeof(tmpdata))); } else { CHECK_RET(copy_to_user((int __user *)pucmd_data->out_data,tmpdata,sizeof(tmpdata))); } } else { dev_err(glue->dev,"%s: error : %d",__FUNCTION__,ret); retval = -1; } } return retval; } static long ssv_sdiobridge_ioctl_writeReg(struct ssv_sdiobridge_glue *glue,unsigned int cmd, struct ssv_sdiobridge_cmd *pcmd_data,struct ssv_sdiobridge_cmd *pucmd_data,bool isCompat) { struct sdio_func *func = dev_to_sdio_func(glue->dev); long retval =0; if ( pcmd_data->in_data_len < 8) { retval = -1; } else { u8 tmpdata[8]; int ret = 0; if ( isCompat ) { CHECK_RET(copy_from_user(tmpdata,(int __user *)compat_ptr((unsigned long)pucmd_data->in_data),sizeof(tmpdata))); } else { CHECK_RET(copy_from_user(tmpdata,(int __user *)pucmd_data->in_data,sizeof(tmpdata))); } dev_err(glue->dev,"%s: write reg ADR[%02X%02X%02X%02X] [%02X][%02X][%02X][%02X]\n",__FUNCTION__,tmpdata[3],tmpdata[2],tmpdata[1],tmpdata[0], tmpdata[7], tmpdata[6], tmpdata[5], tmpdata[4]); sdio_claim_host(func); ret = sdio_memcpy_toio(func, glue->regIOPort, tmpdata, 8); sdio_release_host(func); if ( ret ) { dev_err(glue->dev,"%s: error : %d",__FUNCTION__,ret); retval = -1; } } return retval; } static int ssv_sdiobridge_device_open(struct inode *inode, struct file *filp) { dev_err(glue->dev,"%s():\n", __FUNCTION__); filp->private_data = glue; return 0; } static int ssv_sdiobridge_device_close(struct inode *inode, struct file *filp) { dev_err(glue->dev,"%s():\n", __FUNCTION__); return 0; } static long ssv_sdiobridge_device_ioctl_process(struct ssv_sdiobridge_glue *glue, unsigned int cmd, struct ssv_sdiobridge_cmd *pucmd_data,bool isCompat) { struct ssv_sdiobridge_cmd cmd_data; long retval=0; if ( isCompat ) { CHECK_RET(copy_from_user(&cmd_data,(int __user *)pucmd_data,sizeof(*pucmd_data))); } else { CHECK_RET(copy_from_user(&cmd_data,(int __user *)pucmd_data,sizeof(*pucmd_data))); } #if 0 #ifdef __x86_64 dev_err(glue->dev,"%s: isCompat[%d] [%lX] [%lX] [%X] \n",__FUNCTION__,isCompat,IOCTL_SSVSDIO_GET_FUNCTION_FOCUS,IOCTL_SSVSDIO_READ_DATA,cmd); #else dev_err(glue->dev,"%s: isCompat[%d] [%X] [%X] [%X] \n",__FUNCTION__,isCompat,IOCTL_SSVSDIO_GET_FUNCTION_FOCUS,IOCTL_SSVSDIO_READ_DATA,cmd); #endif #endif switch (cmd) { case IOCTL_SSVSDIO_GET_FUNCTION_FOCUS: retval = ssv_sdiobridge_ioctl_getFuncfocus(glue,cmd,&cmd_data,pucmd_data,isCompat); break; case IOCTL_SSVSDIO_SET_FUNCTION_FOCUS: retval = ssv_sdiobridge_ioctl_setFuncfocus(glue,cmd,&cmd_data,pucmd_data,isCompat); break; case IOCTL_SSVSDIO_GET_BUS_WIDTH: retval = ssv_sdiobridge_ioctl_getBusWidth(glue,cmd,&cmd_data,pucmd_data,isCompat); break; #if 0 case IOCTL_SSVSDIO_SET_BUS_WIDTH: retval = ssv_sdiobridge_ioctl_setBusWidth(glue,cmd,&cmd_data,pucmd_data,isCompat); break; case IOCTL_SSVSDIO_GET_BUS_CLOCK: break; case IOCTL_SSVSDIO_SET_BUS_CLOCK: break; case IOCTL_SSVSDIO_GET_BLOCK_MODE: retval = ssv_sdiobridge_ioctl_getBlockMode(glue,cmd,&cmd_data,pucmd_data,isCompat); break; case IOCTL_SSVSDIO_SET_BLOCK_MODE: retval = ssv_sdiobridge_ioctl_setBlockMode(glue,cmd,&cmd_data,pucmd_data,isCompat); break; #endif case IOCTL_SSVSDIO_GET_BLOCKLEN: retval = ssv_sdiobridge_ioctl_getBlockSize(glue,cmd,&cmd_data,pucmd_data,isCompat); break; case IOCTL_SSVSDIO_SET_BLOCKLEN: retval = ssv_sdiobridge_ioctl_setBlockSize(glue,cmd,&cmd_data,pucmd_data,isCompat); break; case IOCTL_SSVSDIO_READ_BYTE: retval = ssv_sdiobridge_ioctl_readByte(glue,cmd,&cmd_data,pucmd_data,isCompat); break; case IOCTL_SSVSDIO_WRITE_BYTE: retval = ssv_sdiobridge_ioctl_writeByte(glue,cmd,&cmd_data,pucmd_data,isCompat); break; case IOCTL_SSVSDIO_GET_MULTI_BYTE_IO_PORT: retval = ssv_sdiobridge_ioctl_getMultiByteIOPort(glue,cmd,&cmd_data,pucmd_data,isCompat); break; case IOCTL_SSVSDIO_SET_MULTI_BYTE_IO_PORT: retval = ssv_sdiobridge_ioctl_setMultiByteIOPort(glue,cmd,&cmd_data,pucmd_data,isCompat); break; case IOCTL_SSVSDIO_READ_MULTI_BYTE: retval = ssv_sdiobridge_ioctl_readMultiByte(glue,cmd,&cmd_data,pucmd_data,isCompat); break; case IOCTL_SSVSDIO_WRITE_MULTI_BYTE: retval = ssv_sdiobridge_ioctl_writeMultiByte(glue,cmd,&cmd_data,pucmd_data,isCompat); break; case IOCTL_SSVSDIO_GET_MULTI_BYTE_REG_IO_PORT: retval = ssv_sdiobridge_ioctl_getMultiByteRegIOPort(glue,cmd,&cmd_data,pucmd_data,isCompat); break; case IOCTL_SSVSDIO_SET_MULTI_BYTE_REG_IO_PORT: retval = ssv_sdiobridge_ioctl_setMultiByteRegIOPort(glue,cmd,&cmd_data,pucmd_data,isCompat); break; case IOCTL_SSVSDIO_READ_REG: retval = ssv_sdiobridge_ioctl_readReg(glue,cmd,&cmd_data,pucmd_data,isCompat); break; case IOCTL_SSVSDIO_WRITE_REG: retval = ssv_sdiobridge_ioctl_writeReg(glue,cmd,&cmd_data,pucmd_data,isCompat); break; } return retval; } static long ssv_sdiobridge_device_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct ssv_sdiobridge_glue *glue =filp->private_data; long retval=0; struct ssv_sdiobridge_cmd *pucmd_data; pucmd_data = (struct ssv_sdiobridge_cmd *)arg; retval = ssv_sdiobridge_device_ioctl_process( glue,cmd,pucmd_data,true); return retval; } static long ssv_sdiobridge_device_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct ssv_sdiobridge_glue *glue =filp->private_data; long retval=0; struct ssv_sdiobridge_cmd *pucmd_data; pucmd_data = (struct ssv_sdiobridge_cmd *)arg; retval = ssv_sdiobridge_device_ioctl_process( glue,cmd,pucmd_data,false); return retval; } static bool ssv_sdiobridge_have_data(struct ssv_sdiobridge_glue *glue) { dev_err(glue->dev,"%s(): !list_empty(&glue->rxreadybuf)[%d]\n", __FUNCTION__,!list_empty(&glue->rxreadybuf)); return !list_empty(&glue->rxreadybuf); } static ssize_t ssv_sdiobridge_device_read(struct file *filp, char *buffer, size_t length, loff_t *offset) { struct ssv_sdiobridge_glue *glue =filp->private_data; struct ssv_rxbuf *bf; int copylength; dev_err(glue->dev,"%s():\n", __FUNCTION__); spin_lock_bh(&glue->rxbuflock); if (list_empty(&glue->rxreadybuf)) { spin_unlock_bh(&glue->rxbuflock); dev_err(glue->dev,"%s():no data for read \n", __FUNCTION__); #if 1 if ( wait_event_interruptible(glue->read_wq, ssv_sdiobridge_have_data(glue))!=0) { dev_err(glue->dev,"%s():not get data ?? \n", __FUNCTION__); return -1; } #else wait_event(glue->read_wq,ssv_sdiobridge_have_data(glue)); #endif spin_lock_bh(&glue->rxbuflock); if (list_empty(&glue->rxreadybuf)) { spin_unlock_bh(&glue->rxbuflock); dev_err(glue->dev,"%s():stop ?? \n", __FUNCTION__); return -1; } } bf = list_first_entry(&glue->rxreadybuf, struct ssv_rxbuf, list); list_del(&bf->list); spin_unlock_bh(&glue->rxbuflock); copylength = min(bf->rxsize,(u32)length); CHECK_RET(copy_to_user((int __user *)buffer,bf->rxdata,copylength)); dev_err(glue->dev,"%s():get rx data : data len:[%d], user read len:[%d],real read len:[%d] \n", __FUNCTION__,bf->rxsize,(u32)length,copylength); spin_lock_bh(&glue->rxbuflock); list_add_tail(&bf->list, &glue->rxbuf); spin_unlock_bh(&glue->rxbuflock); return copylength; } static ssize_t ssv_sdiobridge_device_write(struct file *filp, const char *buff, size_t len, loff_t *off) { struct ssv_sdiobridge_glue *glue =filp->private_data; struct sdio_func *func = dev_to_sdio_func(glue->dev); u8 *tmpdata; int ret; dev_err(glue->dev,"%s():\n", __FUNCTION__); tmpdata = kzalloc(len, GFP_KERNEL); if ( tmpdata == NULL ) { dev_err(glue->dev,"%s: error : alloc buf error size:%d",__FUNCTION__,(u32)len); return -1; } CHECK_RET(copy_from_user(tmpdata,(int __user *)buff,len)); if (unlikely(glue->dump)) { printk(KERN_DEBUG "%s: WRITE data address[%08x] len[%d] readsize[%d]\n",__FUNCTION__,glue->dataIOPort,(int)len,sdio_align_size(func,len)); print_hex_dump(KERN_DEBUG, "ssv_sdio: WRITE ", DUMP_PREFIX_OFFSET, 16, 1, tmpdata, len, false); } sdio_claim_host(func); ret = sdio_memcpy_toio(func, glue->dataIOPort,tmpdata, sdio_align_size(func,len)); sdio_release_host(func); kfree(tmpdata); if ( ret ) { dev_err(glue->dev,"%s: error : %d",__FUNCTION__,ret); return -1; } return len; } struct file_operations fops = { .owner = THIS_MODULE, .read = ssv_sdiobridge_device_read, .write = ssv_sdiobridge_device_write, .open = ssv_sdiobridge_device_open, .release = ssv_sdiobridge_device_close, .compat_ioctl = ssv_sdiobridge_device_compat_ioctl, .unlocked_ioctl = ssv_sdiobridge_device_ioctl }; static void ssv_sdiobridge_irq_process(struct sdio_func *func, struct ssv_sdiobridge_glue *glue) { int err_ret; u8 status; sdio_claim_host(func); status = sdio_readb(func, REG_INT_STATUS, &err_ret); if ( status & SSVCABRIO_INT_RX ) { struct ssv_rxbuf *bf; int readsize; spin_lock_bh(&glue->rxbuflock); if (list_empty(&glue->rxbuf)) { spin_unlock_bh(&glue->rxbuflock); sdio_release_host(func); dev_err(glue->dev, "ssv_sdiobridge_irq_process no avaible rx buf list??\n"); return; } else { bf = list_first_entry(&glue->rxbuf, struct ssv_rxbuf, list); list_del(&bf->list); } spin_unlock_bh(&glue->rxbuflock); bf->rxsize = (int)(sdio_readb(func, REG_CARD_PKT_LEN_0, &err_ret)&0xff); dev_err(glue->dev, "sdio read rx size[%08x] 0x10[%02x]\n",bf->rxsize, sdio_readb(func, REG_CARD_PKT_LEN_0, &err_ret)&0xff); bf->rxsize = bf->rxsize | ((sdio_readb(func, REG_CARD_PKT_LEN_1, &err_ret)&0xff)<<0x8); readsize = sdio_align_size(func,bf->rxsize); dev_err(glue->dev, "sdio read rx size[%08x] 0x11[%02x] readsize[%d]\n",bf->rxsize, sdio_readb(func, REG_CARD_PKT_LEN_1, &err_ret)&0xff,readsize); err_ret = sdio_memcpy_fromio(func, bf->rxdata, glue->dataIOPort, readsize); sdio_release_host(func); dev_err(glue->dev, "ssv_sdiobridge_irq_process read 53, %d bytes ret:[%d]\n", readsize,err_ret ); if (unlikely(glue->dump)) { printk(KERN_DEBUG "ssv_sdiobridge_irq_process: READ data address[%08x] len[%d] readsize[%d]\n",glue->dataIOPort,(int)bf->rxsize,readsize); print_hex_dump(KERN_DEBUG, "ssv_sdio: READ ", DUMP_PREFIX_OFFSET, 16, 1, bf->rxdata, bf->rxsize, false); } if (WARN_ON(err_ret)) { dev_err(glue->dev, "ssv_sdiobridge_irq_process read failed (%d)\n", err_ret); spin_lock_bh(&glue->rxbuflock); list_add_tail(&bf->list, &glue->rxbuf); spin_unlock_bh(&glue->rxbuflock); } else { spin_lock_bh(&glue->rxbuflock); list_add_tail(&bf->list, &glue->rxreadybuf); wake_up(&glue->read_wq); spin_unlock_bh(&glue->rxbuflock); } } else { sdio_release_host(func); } } static void ssv_sdiobridge_irq_handler(struct sdio_func *func) { struct ssv_sdiobridge_glue *glue = sdio_get_drvdata(func); dev_err(&func->dev, "ssv_sdiobridge_irq_handler\n"); WARN_ON(glue == NULL); if ( glue != NULL ) { atomic_set(&glue->irq_handling, 1); ssv_sdiobridge_irq_process(func,glue); atomic_set(&glue->irq_handling, 0); wake_up(&glue->irq_wq); } } static void ssv_sdiobridge_irq_enable(struct sdio_func *func, struct ssv_sdiobridge_glue *glue) { int ret; func = dev_to_sdio_func(glue->dev); sdio_claim_host(func); dev_err(glue->dev, "ssv_sdiobridge_irq_enable\n"); ret = sdio_claim_irq(func, ssv_sdiobridge_irq_handler); if (ret) dev_err(glue->dev, "Failed to claim sdio irq: %d\n", ret); sdio_release_host(func); } static bool ssv_sdiobridge_is_on_irq(struct ssv_sdiobridge_glue *glue) { return !atomic_read(&glue->irq_handling); } static void ssv_sdiobridge_irq_disable(struct ssv_sdiobridge_glue *glue,bool iswaitirq) { struct sdio_func *func; int ret; dev_err(glue->dev, "ssv_sdiobridge_irq_disable1\n"); if ( glue != NULL ) { func = dev_to_sdio_func(glue->dev); sdio_claim_host(func); dev_err(glue->dev, "ssv_sdiobridge_irq_disable2 [%d]\n",atomic_read(&glue->irq_handling)); if (atomic_read(&glue->irq_handling)&&iswaitirq) { dev_err(glue->dev, "ssv_sdiobridge_irq_disable3\n"); sdio_release_host(func); ret = wait_event_interruptible(glue->irq_wq, ssv_sdiobridge_is_on_irq(glue)); dev_err(glue->dev, "ssv_sdiobridge_irq_disable4 ret[%d]\n",ret); if (ret) return; sdio_claim_host(func); } dev_err(glue->dev, "ssv_sdiobridge_irq_disable5\n"); ret = sdio_release_irq(func); if (ret) dev_err(glue->dev, "Failed to release sdio irq: %d\n", ret); dev_err(glue->dev, "ssv_sdiobridge_irq_disable6\n"); sdio_release_host(func); } } #if 0 static void ssv_sdiobridge_irq_sync(struct device *child) { struct ssv_sdiobridge_glue *glue = dev_get_drvdata(child->parent); struct sdio_func *func; int ret; if ( glue != NULL ) { func = dev_to_sdio_func(glue->dev); sdio_claim_host(func); if (atomic_read(&glue->irq_handling)) { sdio_release_host(func); ret = wait_event_interruptible(glue->irq_wq, ssv_sdiobridge_is_on_irq(glue)); if (ret) return; sdio_claim_host(func); } sdio_release_host(func); } } #endif static void ssv_sdiobridge_read_parameter(struct sdio_func *func, struct ssv_sdiobridge_glue *glue) { int err_ret; sdio_claim_host(func); glue->dataIOPort = 0; glue->dataIOPort = glue->dataIOPort | (sdio_readb(func, REG_DATA_IO_PORT_0, &err_ret) << ( 8*0 )); glue->dataIOPort = glue->dataIOPort | (sdio_readb(func, REG_DATA_IO_PORT_1, &err_ret) << ( 8*1 )); glue->dataIOPort = glue->dataIOPort | (sdio_readb(func, REG_DATA_IO_PORT_2, &err_ret) << ( 8*2 )); glue->regIOPort = 0; glue->regIOPort = glue->regIOPort | (sdio_readb(func, REG_REG_IO_PORT_0, &err_ret) << ( 8*0 )); glue->regIOPort = glue->regIOPort | (sdio_readb(func, REG_REG_IO_PORT_1, &err_ret) << ( 8*1 )); glue->regIOPort = glue->regIOPort | (sdio_readb(func, REG_REG_IO_PORT_2, &err_ret) << ( 8*2 )); dev_err(&func->dev, "dataIOPort 0x%x regIOPort 0x%x [%lx]\n", glue->dataIOPort,glue->regIOPort,(long unsigned int)IOCTL_SSVSDIO_GET_BLOCKLEN); sdio_set_block_size(func,glue->blockSize); sdio_release_host(func); } static int ssv_sdiobridge_init_buf(struct ssv_sdiobridge_glue *glue) { u32 bsize,i,error; struct ssv_rxbuf *bf; init_waitqueue_head(&glue->read_wq); spin_lock_init(&glue->rxbuflock); INIT_LIST_HEAD(&glue->rxbuf); INIT_LIST_HEAD(&glue->rxreadybuf); bsize = sizeof(struct ssv_rxbuf) * RXBUFSIZE; glue->bufaddr = kzalloc(bsize, GFP_KERNEL); if (glue->bufaddr == NULL) { error = -ENOMEM; goto fail; } bf = glue->bufaddr; for (i = 0; i < RXBUFSIZE; i++, bf++) { list_add_tail(&bf->list, &glue->rxbuf); } return 0; fail: return error; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,4,0) static char *ssv_sdiobridge_devnode(struct device *dev, umode_t *mode) #else static char *ssv_sdiobridge_devnode(struct device *dev, mode_t *mode) #endif { if (!mode) return NULL; *mode = 0666; return NULL; } extern int ssv_devicetype; static int __devinit ssv_sdiobridge_probe(struct sdio_func *func, const struct sdio_device_id *id) { mmc_pm_flag_t mmcflags; int ret = -ENOMEM; dev_t dev; int alloc_ret = 0; int cdev_ret = 0; int err_ret; if (ssv_devicetype != 1) { printk(KERN_INFO "Not using SSV6200 bridge SDIO driver.\n"); return -ENODEV; } printk(KERN_INFO "=======================================\n"); printk(KERN_INFO "== RUN SDIO BRIDGE ==\n"); printk(KERN_INFO "=======================================\n"); printk(KERN_INFO "ssv_sdiobridge_probe func->num:%d",func->num); if (func->num != 0x01) return -ENODEV; glue = kzalloc(sizeof(*glue), GFP_KERNEL); if (!glue) { dev_err(&func->dev, "can't allocate glue\n"); goto out; } glue->blockMode = false; glue->blockSize = BLOCKSIZE; glue->autoAckInt = true; glue->dump = false; glue->funcFocus = 1; ssv_sdiobridge_init_buf(glue); glue->dev = &func->dev; func->card->quirks |= MMC_QUIRK_LENIENT_FN0; func->card->quirks |= MMC_QUIRK_BLKSZ_FOR_BYTE_MODE; mmcflags = sdio_get_host_pm_caps(func); dev_err(glue->dev, "sdio PM caps = 0x%x\n", mmcflags); sdio_set_drvdata(func, glue); pm_runtime_put_noidle(&func->dev); ssv_sdiobridge_read_parameter(func,glue); dev = MKDEV(ssv_sdiobridge_ioctl_major, 0); alloc_ret = alloc_chrdev_region(&dev, 0, num_of_dev, FILE_DEVICE_SSVSDIO_NAME); if (alloc_ret) goto error; ssv_sdiobridge_ioctl_major = MAJOR(dev); cdev_init(&ssv_sdiobridge_ioctl_cdev, &fops); cdev_ret = cdev_add(&ssv_sdiobridge_ioctl_cdev, dev, num_of_dev); if (cdev_ret) goto error; fc=class_create(THIS_MODULE, FILE_DEVICE_SSVSDIO_NAME); fc->devnode = ssv_sdiobridge_devnode; device_create(fc, NULL, dev, NULL, "%s", FILE_DEVICE_SSVSDIO_NAME); dev_err(glue->dev, "%s driver(major: %d) installed.\n", FILE_DEVICE_SSVSDIO_NAME, ssv_sdiobridge_ioctl_major); init_waitqueue_head(&glue->irq_wq); ssv_sdiobridge_irq_enable(func,glue); sdio_claim_host(func); #ifdef CONFIG_SSV_SDIO_EXT_INT sdio_writeb(func,(~(SSVCABRIO_INT_RX)|SSVCABRIO_INT_GPIO)&0x7, 0x04, &err_ret); #else sdio_writeb(func,(~(SSVCABRIO_INT_RX|SSVCABRIO_INT_GPIO))&0x7, 0x04, &err_ret); #endif sdio_release_host(func); return 0; error: if (cdev_ret == 0) cdev_del(&ssv_sdiobridge_ioctl_cdev); if (alloc_ret == 0) unregister_chrdev_region(dev, num_of_dev); kfree(glue); out: return ret; } static void __devexit ssv_sdiobridge_remove(struct sdio_func *func) { struct ssv_sdiobridge_glue *glue = sdio_get_drvdata(func); dev_t dev; int err_ret; sdio_claim_host(func); #ifdef CONFIG_SSV_SDIO_EXT_INT sdio_writeb(func,0, 0x04, &err_ret); #else sdio_writeb(func,SSVCABRIO_INT_GPIO, 0x04, &err_ret); #endif sdio_release_host(func); ssv_sdiobridge_irq_disable(glue,false); dev = MKDEV(ssv_sdiobridge_ioctl_major, 0); device_destroy(fc,dev); class_destroy(fc); cdev_del(&ssv_sdiobridge_ioctl_cdev); unregister_chrdev_region(dev, num_of_dev); pm_runtime_get_noresume(&func->dev); if ( glue ) { dev_err(glue->dev, "ssv_sdiobridge_remove"); if (glue->bufaddr) { kfree(glue->bufaddr); } kfree(glue); glue = NULL; } sdio_set_drvdata(func, NULL); } #ifdef CONFIG_PM static int ssv_sdiobridge_suspend(struct device *dev) { int ret = 0; return ret; } static int ssv_sdiobridge_resume(struct device *dev) { dev_dbg(dev, "ssvcabrio resume\n"); return 0; } static const struct dev_pm_ops ssv_sdiobridge_pm_ops = { .suspend = ssv_sdiobridge_suspend, .resume = ssv_sdiobridge_resume, }; #endif static struct sdio_driver ssv_sdio_bridge_driver = { .name = "ssv_sdio_bridge", .id_table = ssv_sdiobridge_devices, .probe = ssv_sdiobridge_probe, .remove = __devexit_p(ssv_sdiobridge_remove), #ifdef CONFIG_PM .drv = { .pm = &ssv_sdiobridge_pm_ops, }, #endif }; EXPORT_SYMBOL(ssv_sdio_bridge_driver); #if 1 static int __init ssv_sdiobridge_init(void) { printk(KERN_INFO "ssv_sdiobridge_init\n"); return sdio_register_driver(&ssv_sdio_bridge_driver); } static void __exit ssv_sdiobridge_exit(void) { printk(KERN_INFO "ssv_sdiobridge_exit\n"); sdio_unregister_driver(&ssv_sdio_bridge_driver); } module_init(ssv_sdiobridge_init); module_exit(ssv_sdiobridge_exit); #endif MODULE_LICENSE("GPL"); MODULE_AUTHOR("iComm Semiconductor Co., Ltd");