/* * 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 "ssv_huw.h" #define SSV6200_ID_NUMBER (128) #define BLOCKSIZE 0x40 #define RXBUFLENGTH 1024*3 #define RXBUFSIZE 512 #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) #define SMAC_SRAM_WRITE(_s,_r,_v,_sz) \ (_s)->hci.hci_ops->hci_write_sram(_r, _v, _sz) #define SMAC_REG_WRITE(_s,_r,_v) \ (_s)->hci.hci_ops->hci_write_word(_r, _v) #define SMAC_REG_READ(_s,_r,_v) \ (_s)->hci.hci_ops->hci_read_word(_r, _v) #define HCI_START(_sh) \ (_sh)->hci.hci_ops->hci_start() #define HCI_STOP(_sh) \ (_sh)->hci.hci_ops->hci_stop() #define HCI_SEND(_sh,_sk,_q) \ (_sh)->hci.hci_ops->hci_tx(_sk, _q, HCI_FLAGS_NO_FLOWCTRL) #define HCI_PAUSE(_sh,_mk) \ (_sh)->hci.hci_ops->hci_tx_pause(_mk) #define HCI_RESUME(_sh,_mk) \ (_sh)->hci.hci_ops->hci_tx_resume(_mk) #define HCI_TXQ_FLUSH(_sh,_mk) \ (_sh)->hci.hci_ops->hci_txq_flush(_mk) #define HCI_TXQ_FLUSH_BY_STA(_sh,_aid) \ (_sh)->hci.hci_ops->hci_txq_flush_by_sta(_aid) #define HCI_TXQ_EMPTY(_sh,_txqid) \ (_sh)->hci.hci_ops->hci_txq_empty(_txqid) #define HCI_WAKEUP_PMU(_sh) \ (_sh)->hci.hci_ops->hci_pmu_wakeup() #define HCI_SEND_CMD(_sh,_sk) \ (_sh)->hci.hci_ops->hci_send_cmd(_sk) struct ssv_huw_dev { struct device *dev; struct ssv6xxx_platform_data *priv; struct ssv6xxx_hci_info hci; char chip_id[24]; u64 chip_tag; u8 funcFocus; wait_queue_head_t read_wq; spinlock_t rxlock; void *bufaddr; struct sk_buff_head rx_skb_q; }; struct ssv_rxbuf { struct list_head list; u32 rxsize; u8 rxdata[RXBUFLENGTH]; }; struct ssv_huw_dev g_huw_dev; 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; #if !defined(USE_THREAD_RX) || defined(USE_BATCH_RX) int ssv_huw_rx(struct sk_buff_head *rx_skb_q, void *args) #else int ssv_huw_rx(struct sk_buff *rx_skb, void *args) #endif { struct ssv_huw_dev *phuw_dev = (struct ssv_huw_dev *)args; unsigned long flags; spin_lock_irqsave(&phuw_dev->rxlock, flags); #if !defined(USE_THREAD_RX) || defined(USE_BATCH_RX) while (skb_queue_len(rx_skb_q)) __skb_queue_tail(&phuw_dev->rx_skb_q, __skb_dequeue(rx_skb_q)); #else __skb_queue_tail(&phuw_dev->rx_skb_q, rx_skb); #endif spin_unlock_irqrestore(&phuw_dev->rxlock, flags); wake_up_interruptible(&phuw_dev->read_wq); return 0; } void ssv_huw_txbuf_free_skb(struct sk_buff *skb, void *args) { if (!skb) return; dev_kfree_skb_any(skb); } unsigned int skb_queue_len_bhsafe(struct sk_buff_head *head, spinlock_t *plock) { unsigned int len = 0; spin_lock_bh(plock); len = skb_queue_len(head); spin_unlock_bh(plock); return len; } static long ssv_huw_ioctl_readReg(struct ssv_huw_dev *phuw_dev,unsigned int cmd, struct ssv_huw_cmd *pcmd_data,struct ssv_huw_cmd *pucmd_data,bool isCompat) { long retval =0; if ( pcmd_data->in_data_len < 4 || pcmd_data->out_data_len < 4) { retval = -1; } else { u32 tmpdata; u32 regval; int ret = 0; #ifdef CONFIG_COMPAT if ( isCompat ) { CHECK_RET(copy_from_user(&tmpdata,(int __user *)compat_ptr((unsigned long)pucmd_data->in_data),sizeof(tmpdata))); } else #endif { CHECK_RET(copy_from_user(&tmpdata,(int __user *)pucmd_data->in_data,sizeof(tmpdata))); } ret = SMAC_REG_READ(phuw_dev, tmpdata, ®val); if ( !ret ) { #ifdef CONFIG_COMPAT if ( isCompat ) { CHECK_RET(copy_to_user((int __user *)compat_ptr((unsigned long)pucmd_data->out_data),®val,sizeof(regval))); } else #endif { CHECK_RET(copy_to_user((int __user *)pucmd_data->out_data,®val,sizeof(regval))); } } else { dev_err(phuw_dev->dev,"%s: error : %d",__FUNCTION__,ret); retval = -1; } } return retval; } static long ssv_huw_ioctl_writeReg(struct ssv_huw_dev *phuw_dev,unsigned int cmd, struct ssv_huw_cmd *pcmd_data,struct ssv_huw_cmd *pucmd_data,bool isCompat) { long retval =0; if ( pcmd_data->in_data_len < 8) { retval = -1; } else { u32 tmpdata[2]; int ret = 0; #ifdef CONFIG_COMPAT if ( isCompat ) { CHECK_RET(copy_from_user(&tmpdata,(int __user *)compat_ptr((unsigned long)pucmd_data->in_data),sizeof(tmpdata))); } else #endif { CHECK_RET(copy_from_user(&tmpdata,(int __user *)pucmd_data->in_data,sizeof(tmpdata))); } SMAC_REG_WRITE(phuw_dev, tmpdata[0], tmpdata[1]); if ( ret ) { dev_err(phuw_dev->dev,"%s: error : %d",__FUNCTION__,ret); retval = -1; } } return retval; } static long ssv_huw_ioctl_writeSram(struct ssv_huw_dev *phuw_dev,unsigned int cmd, struct ssv_huw_cmd *pcmd_data,struct ssv_huw_cmd *pucmd_data,bool isCompat) { long retval =0; unsigned char *ptr = NULL; unsigned int addr; if (( pcmd_data->in_data_len != 4) || ( pcmd_data->out_data_len <= 0)) { retval = -1; } else { int ret = 0; ptr = kzalloc(pcmd_data->out_data_len, GFP_KERNEL); if(ptr == NULL) return -ENOMEM; #ifdef CONFIG_COMPAT if ( isCompat ) { CHECK_RET(copy_from_user(&addr, (int __user *)compat_ptr((unsigned long)pucmd_data->in_data), sizeof(addr))); CHECK_RET(copy_from_user(ptr, (int __user *)compat_ptr((unsigned long)pucmd_data->out_data), pcmd_data->out_data_len)); } else #endif { CHECK_RET(copy_from_user(&addr, (int __user *)pucmd_data->in_data, sizeof(addr))); CHECK_RET(copy_from_user(ptr, (int __user *)pucmd_data->out_data, pcmd_data->out_data_len)); } SMAC_SRAM_WRITE(phuw_dev, addr, ptr, pcmd_data->out_data_len); if ( ret ) { dev_err(phuw_dev->dev,"%s: error : %d",__FUNCTION__,ret); retval = -1; } kfree(ptr); } return retval; } static long ssv_huw_ioctl_process(struct ssv_huw_dev *glue, unsigned int cmd, struct ssv_huw_cmd *pucmd_data, bool isCompat) { struct ssv_huw_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))); } switch (cmd) { case IOCTL_SSVSDIO_READ_REG: retval = ssv_huw_ioctl_readReg(glue,cmd,&cmd_data,pucmd_data,isCompat); break; case IOCTL_SSVSDIO_WRITE_REG: retval = ssv_huw_ioctl_writeReg(glue,cmd,&cmd_data,pucmd_data,isCompat); break; case IOCTL_SSVSDIO_WRITE_SRAM: retval = ssv_huw_ioctl_writeSram(glue,cmd,&cmd_data,pucmd_data,isCompat); break; case IOCTL_SSVSDIO_START: retval = HCI_START(glue); break; case IOCTL_SSVSDIO_STOP: retval = HCI_STOP(glue); break; default: return -EINVAL; } return retval; } #ifdef CONFIG_COMPAT static long ssv_huw_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { long retval=0; struct ssv_huw_cmd *pucmd_data; pucmd_data = (struct ssv_huw_cmd *)arg; retval = ssv_huw_ioctl_process(&g_huw_dev, cmd, pucmd_data, true); return retval; } #endif static long ssv_huw_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { long retval=0; struct ssv_huw_cmd *pucmd_data; pucmd_data = (struct ssv_huw_cmd *)arg; retval = ssv_huw_ioctl_process( &g_huw_dev,cmd,pucmd_data,false); return retval; } static int ssv_huw_open(struct inode *inode, struct file *fp) { fp->private_data = &g_huw_dev; return 0; } static int ssv_huw_release(struct inode *inode, struct file *fp) { struct sk_buff *skb = NULL; while((skb = skb_dequeue(&(g_huw_dev.rx_skb_q))) != NULL) { dev_kfree_skb_any(skb); skb = NULL; } return 0; } static ssize_t ssv_huw_read(struct file *fp, char __user * buf, size_t length, loff_t * offset) { int ret = 0, copy_length = 0; struct sk_buff *skb = NULL; if (skb_queue_len_bhsafe(&(g_huw_dev.rx_skb_q), &(g_huw_dev.rxlock)) == 0) { ret = wait_event_interruptible((g_huw_dev.read_wq), (skb_queue_len_bhsafe(&(g_huw_dev.rx_skb_q), &(g_huw_dev.rxlock)) != 0)); if (ret != 0) return -1; } spin_lock_bh(&(g_huw_dev.rxlock)); if (skb_queue_len(&(g_huw_dev.rx_skb_q)) > 0) skb = skb_dequeue(&(g_huw_dev.rx_skb_q)); spin_unlock_bh(&(g_huw_dev.rxlock)); if (skb != NULL) { copy_length = min(skb->len,(u32)length); CHECK_RET(copy_to_user((int __user *)buf, skb->data, copy_length)); dev_kfree_skb_any(skb); } return copy_length; } static ssize_t ssv_huw_write(struct file *fp, const char __user * buf, size_t length, loff_t * offset) { struct sk_buff *skb; unsigned int len = (unsigned int)length; len = (len & 0x1f)?(((len>>5) + 1)<<5):len; skb = __dev_alloc_skb(len, GFP_KERNEL); if (skb == NULL) { dev_err(g_huw_dev.dev,"%s: error : alloc buf error size:%d",__FUNCTION__,(u32)len); return -ENOMEM; } CHECK_RET(copy_from_user(skb->data, (int __user *)buf, length)); skb_put(skb, length); HCI_SEND(&g_huw_dev, skb, 1); return length; } void ssv_huw_tx_cb(struct sk_buff_head *skb_head, void *args) { struct sk_buff *skb = NULL; while ((skb=skb_dequeue(skb_head))) { dev_kfree_skb_any(skb); skb = NULL; } } int ssv_huw_read_hci_info(struct ssv_huw_dev *phuw_dev) { struct ssv6xxx_hci_info *pinfo = &(phuw_dev->hci); pinfo->hci_ops = NULL; pinfo->dev = phuw_dev->dev; pinfo->hci_rx_cb = ssv_huw_rx; pinfo->rx_cb_args = (void *)phuw_dev; pinfo->hci_tx_cb= ssv_huw_tx_cb; pinfo->tx_cb_args = NULL; pinfo->hci_skb_update_cb = NULL; pinfo->skb_update_args = NULL; pinfo->hci_tx_flow_ctrl_cb = NULL; pinfo->tx_fctrl_cb_args = NULL; pinfo->hci_tx_q_empty_cb = NULL; pinfo->tx_q_empty_args = NULL; pinfo->hci_tx_buf_free_cb = ssv_huw_txbuf_free_skb; pinfo->tx_buf_free_args = NULL; pinfo->if_ops = phuw_dev->priv->ops; return 0; } struct file_operations s_huw_ops = { .read = ssv_huw_read, .write = ssv_huw_write, .unlocked_ioctl = ssv_huw_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = ssv_huw_compat_ioctl, #endif .open = ssv_huw_open, .release = ssv_huw_release, }; static int ssv_huw_init_buf(struct ssv_huw_dev *hdev) { init_waitqueue_head(&hdev->read_wq); spin_lock_init(&hdev->rxlock); skb_queue_head_init(&(hdev->rx_skb_q)); return 0; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,4,0) static char *ssv_huw_devnode(struct device *dev, umode_t *mode) #else static char *ssv_huw_devnode(struct device *dev, mode_t *mode) #endif { if (!mode) return NULL; *mode = 0666; return NULL; } int ssv_huw_probe(struct platform_device *pdev) { dev_t dev; int alloc_ret = 0; int cdev_ret = 0; if (!pdev->dev.platform_data) { dev_err(&pdev->dev, "no platform data specified!\n"); return -EINVAL; } ssv_huw_init_buf(&g_huw_dev); g_huw_dev.priv = (pdev->dev.platform_data); g_huw_dev.dev = &(pdev->dev); ssv_huw_read_hci_info(&g_huw_dev); ssv6xxx_hci_register(&(g_huw_dev.hci)); 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, &s_huw_ops); 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_huw_devnode; device_create(fc,NULL,dev,NULL,"%s",FILE_DEVICE_SSVSDIO_NAME); dev_err(&pdev->dev, "%s driver(major: %d) installed.\n", FILE_DEVICE_SSVSDIO_NAME, ssv_sdiobridge_ioctl_major); return 0; error: if (cdev_ret == 0) cdev_del(&ssv_sdiobridge_ioctl_cdev); if (alloc_ret == 0) unregister_chrdev_region(dev, num_of_dev); return -ENODEV; } EXPORT_SYMBOL(ssv_huw_probe); int ssv_huw_remove(struct platform_device *pdev) { dev_t dev; int ret = 0; ssv6xxx_hci_deregister(); memset(&g_huw_dev, 0 , sizeof(g_huw_dev)); 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); return ret; } EXPORT_SYMBOL(ssv_huw_remove); static const struct platform_device_id huw_id_table[] = { { .name = "ssv6200", .driver_data = 0x00, }, {}, }; MODULE_DEVICE_TABLE(platform, huw_id_table); static struct platform_driver ssv_huw_driver = { .probe = ssv_huw_probe, #if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0) .remove = __devexit_p(ssv_huw_remove), #else .remove = ssv_huw_remove, #endif .id_table = huw_id_table, .driver = { .name = "SSV WLAN driver", .owner = THIS_MODULE, } }; #if (defined(CONFIG_SSV_SUPPORT_ANDROID)||defined(CONFIG_SSV_BUILD_AS_ONE_KO)) int ssv_huw_init(void) #else static int __init ssv_huw_init(void) #endif { int ret; memset(&g_huw_dev, 0 , sizeof(g_huw_dev)); ret = platform_driver_register(&ssv_huw_driver); if (ret < 0) { printk(KERN_ALERT "[HCI user-space wrapper]: Fail to register huw\n"); } return ret; } #if (defined(CONFIG_SSV_SUPPORT_ANDROID)||defined(CONFIG_SSV_BUILD_AS_ONE_KO)) void ssv_huw_exit(void) #else static void __exit ssv_huw_exit(void) #endif { platform_driver_unregister(&ssv_huw_driver); } #if (defined(CONFIG_SSV_SUPPORT_ANDROID)||defined(CONFIG_SSV_BUILD_AS_ONE_KO)) EXPORT_SYMBOL(ssv_huw_init); EXPORT_SYMBOL(ssv_huw_exit); #else module_init(ssv_huw_init); module_exit(ssv_huw_exit); #endif MODULE_LICENSE("GPL");