/*
* 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");