2009-05-21 21:20:45 +08:00
/*
* Intel Wireless Multicomm 3200 WiFi driver
*
* Copyright ( C ) 2009 Intel Corporation . All rights reserved .
*
* Redistribution and use in source and binary forms , with or without
* modification , are permitted provided that the following conditions
* are met :
*
* * Redistributions of source code must retain the above copyright
* notice , this list of conditions and the following disclaimer .
* * Redistributions in binary form must reproduce the above copyright
* notice , this list of conditions and the following disclaimer in
* the documentation and / or other materials provided with the
* distribution .
* * Neither the name of Intel Corporation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission .
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* " AS IS " AND ANY EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT
* LIMITED TO , THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED . IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT , INDIRECT , INCIDENTAL ,
* SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES ( INCLUDING , BUT NOT
* LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ; LOSS OF USE ,
* DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY , OR TORT
* ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
*
*
* Intel Corporation < ilw @ linux . intel . com >
* Samuel Ortiz < samuel . ortiz @ intel . com >
* Zhu Yi < yi . zhu @ intel . com >
*
*/
/*
* This is the SDIO bus specific hooks for iwm .
* It also is the module ' s entry point .
*
* Interesting code paths :
* iwm_sdio_probe ( ) ( Called by an SDIO bus scan )
* - > iwm_if_alloc ( ) ( netdev . c )
* - > iwm_wdev_alloc ( ) ( cfg80211 . c , allocates and register our wiphy )
* - > wiphy_new ( )
* - > wiphy_register ( )
* - > alloc_netdev_mq ( )
* - > register_netdev ( )
*
* iwm_sdio_remove ( )
* - > iwm_if_free ( ) ( netdev . c )
* - > unregister_netdev ( )
* - > iwm_wdev_free ( ) ( cfg80211 . c )
* - > wiphy_unregister ( )
* - > wiphy_free ( )
*
* iwm_sdio_isr ( ) ( called in process context from the SDIO core code )
* - > queue_work ( . . , isr_worker )
* - - [ async ] - - > iwm_sdio_isr_worker ( )
* - > iwm_rx_handle ( )
*/
# include <linux/kernel.h>
# include <linux/netdevice.h>
# include <linux/debugfs.h>
# include <linux/mmc/sdio.h>
# include <linux/mmc/sdio_func.h>
# include "iwm.h"
# include "debug.h"
# include "bus.h"
# include "sdio.h"
static void iwm_sdio_isr_worker ( struct work_struct * work )
{
struct iwm_sdio_priv * hw ;
struct iwm_priv * iwm ;
struct iwm_rx_info * rx_info ;
struct sk_buff * skb ;
u8 * rx_buf ;
unsigned long rx_size ;
hw = container_of ( work , struct iwm_sdio_priv , isr_worker ) ;
iwm = hw_to_iwm ( hw ) ;
while ( ! skb_queue_empty ( & iwm - > rx_list ) ) {
skb = skb_dequeue ( & iwm - > rx_list ) ;
rx_info = skb_to_rx_info ( skb ) ;
rx_size = rx_info - > rx_size ;
rx_buf = skb - > data ;
IWM_HEXDUMP ( iwm , DBG , SDIO , " RX: " , rx_buf , rx_size ) ;
if ( iwm_rx_handle ( iwm , rx_buf , rx_size ) < 0 )
IWM_WARN ( iwm , " RX error \n " ) ;
kfree_skb ( skb ) ;
}
}
static void iwm_sdio_isr ( struct sdio_func * func )
{
struct iwm_priv * iwm ;
struct iwm_sdio_priv * hw ;
struct iwm_rx_info * rx_info ;
struct sk_buff * skb ;
unsigned long buf_size , read_size ;
int ret ;
u8 val ;
hw = sdio_get_drvdata ( func ) ;
iwm = hw_to_iwm ( hw ) ;
buf_size = hw - > blk_size ;
/* We're checking the status */
val = sdio_readb ( func , IWM_SDIO_INTR_STATUS_ADDR , & ret ) ;
if ( val = = 0 | | ret < 0 ) {
IWM_ERR ( iwm , " Wrong INTR_STATUS \n " ) ;
return ;
}
/* See if we have free buffers */
if ( skb_queue_len ( & iwm - > rx_list ) > IWM_RX_LIST_SIZE ) {
IWM_ERR ( iwm , " No buffer for more Rx frames \n " ) ;
return ;
}
/* We first read the transaction size */
read_size = sdio_readb ( func , IWM_SDIO_INTR_GET_SIZE_ADDR + 1 , & ret ) ;
read_size = read_size < < 8 ;
if ( ret < 0 ) {
IWM_ERR ( iwm , " Couldn't read the xfer size \n " ) ;
return ;
}
/* We need to clear the INT register */
sdio_writeb ( func , 1 , IWM_SDIO_INTR_CLEAR_ADDR , & ret ) ;
if ( ret < 0 ) {
IWM_ERR ( iwm , " Couldn't clear the INT register \n " ) ;
return ;
}
while ( buf_size < read_size )
buf_size < < = 1 ;
skb = dev_alloc_skb ( buf_size ) ;
if ( ! skb ) {
IWM_ERR ( iwm , " Couldn't alloc RX skb \n " ) ;
return ;
}
rx_info = skb_to_rx_info ( skb ) ;
rx_info - > rx_size = read_size ;
rx_info - > rx_buf_size = buf_size ;
/* Now we can read the actual buffer */
ret = sdio_memcpy_fromio ( func , skb_put ( skb , read_size ) ,
IWM_SDIO_DATA_ADDR , read_size ) ;
/* The skb is put on a driver's specific Rx SKB list */
skb_queue_tail ( & iwm - > rx_list , skb ) ;
/* We can now schedule the actual worker */
queue_work ( hw - > isr_wq , & hw - > isr_worker ) ;
}
static void iwm_sdio_rx_free ( struct iwm_sdio_priv * hw )
{
struct iwm_priv * iwm = hw_to_iwm ( hw ) ;
flush_workqueue ( hw - > isr_wq ) ;
skb_queue_purge ( & iwm - > rx_list ) ;
}
/* Bus ops */
static int if_sdio_enable ( struct iwm_priv * iwm )
{
struct iwm_sdio_priv * hw = iwm_to_if_sdio ( iwm ) ;
int ret ;
sdio_claim_host ( hw - > func ) ;
ret = sdio_enable_func ( hw - > func ) ;
if ( ret ) {
IWM_ERR ( iwm , " Couldn't enable the device: is TOP driver "
" loaded and functional? \n " ) ;
goto release_host ;
}
iwm_reset ( iwm ) ;
ret = sdio_claim_irq ( hw - > func , iwm_sdio_isr ) ;
if ( ret ) {
IWM_ERR ( iwm , " Failed to claim irq: %d \n " , ret ) ;
goto release_host ;
}
sdio_writeb ( hw - > func , 1 , IWM_SDIO_INTR_ENABLE_ADDR , & ret ) ;
if ( ret < 0 ) {
IWM_ERR ( iwm , " Couldn't enable INTR: %d \n " , ret ) ;
goto release_irq ;
}
sdio_release_host ( hw - > func ) ;
IWM_DBG_SDIO ( iwm , INFO , " IWM SDIO enable \n " ) ;
return 0 ;
release_irq :
sdio_release_irq ( hw - > func ) ;
release_host :
sdio_release_host ( hw - > func ) ;
return ret ;
}
static int if_sdio_disable ( struct iwm_priv * iwm )
{
struct iwm_sdio_priv * hw = iwm_to_if_sdio ( iwm ) ;
int ret ;
iwm_reset ( iwm ) ;
sdio_claim_host ( hw - > func ) ;
sdio_writeb ( hw - > func , 0 , IWM_SDIO_INTR_ENABLE_ADDR , & ret ) ;
if ( ret < 0 )
IWM_WARN ( iwm , " Couldn't disable INTR: %d \n " , ret ) ;
sdio_release_irq ( hw - > func ) ;
sdio_disable_func ( hw - > func ) ;
sdio_release_host ( hw - > func ) ;
iwm_sdio_rx_free ( hw ) ;
IWM_DBG_SDIO ( iwm , INFO , " IWM SDIO disable \n " ) ;
return 0 ;
}
static int if_sdio_send_chunk ( struct iwm_priv * iwm , u8 * buf , int count )
{
struct iwm_sdio_priv * hw = iwm_to_if_sdio ( iwm ) ;
int aligned_count = ALIGN ( count , hw - > blk_size ) ;
int ret ;
if ( ( unsigned long ) buf & 0x3 ) {
IWM_ERR ( iwm , " buf <%p> is not dword aligned \n " , buf ) ;
/* TODO: Is this a hardware limitation? use get_unligned */
return - EINVAL ;
}
sdio_claim_host ( hw - > func ) ;
ret = sdio_memcpy_toio ( hw - > func , IWM_SDIO_DATA_ADDR , buf ,
aligned_count ) ;
sdio_release_host ( hw - > func ) ;
return ret ;
}
/* debugfs hooks */
static int iwm_debugfs_sdio_open ( struct inode * inode , struct file * filp )
{
filp - > private_data = inode - > i_private ;
return 0 ;
}
static ssize_t iwm_debugfs_sdio_read ( struct file * filp , char __user * buffer ,
size_t count , loff_t * ppos )
{
struct iwm_priv * iwm = filp - > private_data ;
struct iwm_sdio_priv * hw = iwm_to_if_sdio ( iwm ) ;
char * buf ;
u8 cccr ;
int buf_len = 4096 , ret ;
size_t len = 0 ;
if ( * ppos ! = 0 )
return 0 ;
if ( count < sizeof ( buf ) )
return - ENOSPC ;
buf = kzalloc ( buf_len , GFP_KERNEL ) ;
if ( ! buf )
return - ENOMEM ;
sdio_claim_host ( hw - > func ) ;
cccr = sdio_f0_readb ( hw - > func , SDIO_CCCR_IOEx , & ret ) ;
if ( ret ) {
IWM_ERR ( iwm , " Could not read SDIO_CCCR_IOEx \n " ) ;
goto err ;
}
len + = snprintf ( buf + len , buf_len - len , " CCCR_IOEx: 0x%x \n " , cccr ) ;
cccr = sdio_f0_readb ( hw - > func , SDIO_CCCR_IORx , & ret ) ;
if ( ret ) {
IWM_ERR ( iwm , " Could not read SDIO_CCCR_IORx \n " ) ;
goto err ;
}
len + = snprintf ( buf + len , buf_len - len , " CCCR_IORx: 0x%x \n " , cccr ) ;
cccr = sdio_f0_readb ( hw - > func , SDIO_CCCR_IENx , & ret ) ;
if ( ret ) {
IWM_ERR ( iwm , " Could not read SDIO_CCCR_IENx \n " ) ;
goto err ;
}
len + = snprintf ( buf + len , buf_len - len , " CCCR_IENx: 0x%x \n " , cccr ) ;
cccr = sdio_f0_readb ( hw - > func , SDIO_CCCR_INTx , & ret ) ;
if ( ret ) {
IWM_ERR ( iwm , " Could not read SDIO_CCCR_INTx \n " ) ;
goto err ;
}
len + = snprintf ( buf + len , buf_len - len , " CCCR_INTx: 0x%x \n " , cccr ) ;
cccr = sdio_f0_readb ( hw - > func , SDIO_CCCR_ABORT , & ret ) ;
if ( ret ) {
IWM_ERR ( iwm , " Could not read SDIO_CCCR_ABORTx \n " ) ;
goto err ;
}
len + = snprintf ( buf + len , buf_len - len , " CCCR_ABORT: 0x%x \n " , cccr ) ;
cccr = sdio_f0_readb ( hw - > func , SDIO_CCCR_IF , & ret ) ;
if ( ret ) {
IWM_ERR ( iwm , " Could not read SDIO_CCCR_IF \n " ) ;
goto err ;
}
len + = snprintf ( buf + len , buf_len - len , " CCCR_IF: 0x%x \n " , cccr ) ;
cccr = sdio_f0_readb ( hw - > func , SDIO_CCCR_CAPS , & ret ) ;
if ( ret ) {
IWM_ERR ( iwm , " Could not read SDIO_CCCR_CAPS \n " ) ;
goto err ;
}
len + = snprintf ( buf + len , buf_len - len , " CCCR_CAPS: 0x%x \n " , cccr ) ;
cccr = sdio_f0_readb ( hw - > func , SDIO_CCCR_CIS , & ret ) ;
if ( ret ) {
IWM_ERR ( iwm , " Could not read SDIO_CCCR_CIS \n " ) ;
goto err ;
}
len + = snprintf ( buf + len , buf_len - len , " CCCR_CIS: 0x%x \n " , cccr ) ;
ret = simple_read_from_buffer ( buffer , len , ppos , buf , buf_len ) ;
err :
sdio_release_host ( hw - > func ) ;
kfree ( buf ) ;
return ret ;
}
static const struct file_operations iwm_debugfs_sdio_fops = {
. owner = THIS_MODULE ,
. open = iwm_debugfs_sdio_open ,
. read = iwm_debugfs_sdio_read ,
} ;
static int if_sdio_debugfs_init ( struct iwm_priv * iwm , struct dentry * parent_dir )
{
int result ;
struct iwm_sdio_priv * hw = iwm_to_if_sdio ( iwm ) ;
hw - > cccr_dentry = debugfs_create_file ( " cccr " , 0200 ,
parent_dir , iwm ,
& iwm_debugfs_sdio_fops ) ;
result = PTR_ERR ( hw - > cccr_dentry ) ;
if ( IS_ERR ( hw - > cccr_dentry ) & & ( result ! = - ENODEV ) ) {
IWM_ERR ( iwm , " Couldn't create CCCR entry: %d \n " , result ) ;
return result ;
}
return 0 ;
}
static void if_sdio_debugfs_exit ( struct iwm_priv * iwm )
{
struct iwm_sdio_priv * hw = iwm_to_if_sdio ( iwm ) ;
debugfs_remove ( hw - > cccr_dentry ) ;
}
static struct iwm_if_ops if_sdio_ops = {
. enable = if_sdio_enable ,
. disable = if_sdio_disable ,
. send_chunk = if_sdio_send_chunk ,
. debugfs_init = if_sdio_debugfs_init ,
. debugfs_exit = if_sdio_debugfs_exit ,
. umac_name = " iwmc3200wifi-umac-sdio.bin " ,
2009-06-02 15:52:20 +08:00
. calib_lmac_name = " iwmc3200wifi-calib-sdio.bin " ,
2009-05-21 21:20:45 +08:00
. lmac_name = " iwmc3200wifi-lmac-sdio.bin " ,
} ;
static int iwm_sdio_probe ( struct sdio_func * func ,
const struct sdio_device_id * id )
{
struct iwm_priv * iwm ;
struct iwm_sdio_priv * hw ;
struct device * dev = & func - > dev ;
int ret ;
/* check if TOP has already initialized the card */
sdio_claim_host ( func ) ;
ret = sdio_enable_func ( func ) ;
if ( ret ) {
dev_err ( dev , " wait for TOP to enable the device \n " ) ;
sdio_release_host ( func ) ;
return ret ;
}
ret = sdio_set_block_size ( func , IWM_SDIO_BLK_SIZE ) ;
sdio_disable_func ( func ) ;
sdio_release_host ( func ) ;
if ( ret < 0 ) {
dev_err ( dev , " Failed to set block size: %d \n " , ret ) ;
return ret ;
}
iwm = iwm_if_alloc ( sizeof ( struct iwm_sdio_priv ) , dev , & if_sdio_ops ) ;
if ( IS_ERR ( iwm ) ) {
dev_err ( dev , " allocate SDIO interface failed \n " ) ;
return PTR_ERR ( iwm ) ;
}
hw = iwm_private ( iwm ) ;
hw - > iwm = iwm ;
ret = iwm_debugfs_init ( iwm ) ;
if ( ret < 0 ) {
IWM_ERR ( iwm , " Debugfs registration failed \n " ) ;
goto if_free ;
}
sdio_set_drvdata ( func , hw ) ;
hw - > func = func ;
hw - > blk_size = IWM_SDIO_BLK_SIZE ;
hw - > isr_wq = create_singlethread_workqueue ( KBUILD_MODNAME " _sdio " ) ;
if ( ! hw - > isr_wq ) {
ret = - ENOMEM ;
goto debugfs_exit ;
}
INIT_WORK ( & hw - > isr_worker , iwm_sdio_isr_worker ) ;
2009-06-15 21:36:14 +02:00
ret = iwm_if_add ( iwm ) ;
if ( ret ) {
dev_err ( dev , " add SDIO interface failed \n " ) ;
goto destroy_wq ;
}
2009-05-21 21:20:45 +08:00
dev_info ( dev , " IWM SDIO probe \n " ) ;
return 0 ;
2009-06-15 21:36:14 +02:00
destroy_wq :
destroy_workqueue ( hw - > isr_wq ) ;
2009-05-21 21:20:45 +08:00
debugfs_exit :
iwm_debugfs_exit ( iwm ) ;
if_free :
iwm_if_free ( iwm ) ;
return ret ;
}
static void iwm_sdio_remove ( struct sdio_func * func )
{
struct iwm_sdio_priv * hw = sdio_get_drvdata ( func ) ;
struct iwm_priv * iwm = hw_to_iwm ( hw ) ;
struct device * dev = & func - > dev ;
iwm_debugfs_exit ( iwm ) ;
2009-06-15 21:36:14 +02:00
iwm_if_remove ( iwm ) ;
2009-05-21 21:20:45 +08:00
iwm_if_free ( iwm ) ;
destroy_workqueue ( hw - > isr_wq ) ;
sdio_set_drvdata ( func , NULL ) ;
dev_info ( dev , " IWM SDIO remove \n " ) ;
return ;
}
static const struct sdio_device_id iwm_sdio_ids [ ] = {
{ SDIO_DEVICE ( SDIO_VENDOR_ID_INTEL , SDIO_DEVICE_ID_IWM ) } ,
{ /* end: all zeroes */ } ,
} ;
MODULE_DEVICE_TABLE ( sdio , iwm_sdio_ids ) ;
static struct sdio_driver iwm_sdio_driver = {
. name = " iwm_sdio " ,
. id_table = iwm_sdio_ids ,
. probe = iwm_sdio_probe ,
. remove = iwm_sdio_remove ,
} ;
static int __init iwm_sdio_init_module ( void )
{
int ret ;
ret = sdio_register_driver ( & iwm_sdio_driver ) ;
return ret ;
}
static void __exit iwm_sdio_exit_module ( void )
{
sdio_unregister_driver ( & iwm_sdio_driver ) ;
}
module_init ( iwm_sdio_init_module ) ;
module_exit ( iwm_sdio_exit_module ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( IWM_COPYRIGHT " " IWM_AUTHOR ) ;