/* * Mac80211 spi driver for altobeam APOLLO device * * * Copyright (c) 2016, altobeam * Author: * * Based on apollo code Copyright (c) 2010, ST-Ericsson * Author: Dmitry Tarnyagin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #define DEBUG 1 #include #include #include #include #include #include #include #include #include #include #include "apollo.h" #include "sbus.h" #include "apollo_plat.h" #include "debug.h" #include "hwio_spi.h" #include "svn_version.h" MODULE_DESCRIPTION("mac80211 altobeam apollo wifi spi driver"); MODULE_LICENSE("GPL"); MODULE_ALIAS("atbm_wlan"); struct build_info{ int ver; int dpll; char driver_info[64]; }; const char DRIVER_INFO[]={"[====="" """"=====]"}; static int driver_build_info(void) { struct build_info build; build.ver=DRIVER_VER; if (DPLL_CLOCK==1) build.dpll=40; else build.dpll=26; memcpy(build.driver_info,(void*)DRIVER_INFO,sizeof(DRIVER_INFO)); atbm_printk_always("SVN_VER=%d,DPLL_CLOCK=%d,BUILD_TIME=%s\n",build.ver,build.dpll,build.driver_info); #if (PROJ_TYPE==HERA) atbm_printk_always("----drvier support chip HERA \n"); #endif return 0; } #define SELF_BUFLEN (512) struct sbus_priv { struct spi_device *spi; struct atbm_common *core; const struct atbm_platform_data *pdata; spinlock_t lock; spinlock_t spi_rwlock; struct sbus_wtd * wtd; int old_channelFlag; }; struct sbus_wtd { int wtd_init; struct task_struct *wtd_thread; wait_queue_head_t wtd_evt_wq; atomic_t wtd_term; atomic_t wtd_run; atomic_t wtd_probe; atomic_t wtd_spi_read_ready; struct atbm_common *core; }; static int atbm_spi_init(void); static void atbm_spi_exit(void); static struct sbus_wtd g_wtd={ .wtd_init = 0, .wtd_thread = NULL, }; static void spidev_complete(void *arg) { complete(arg); } static ssize_t spidev_sync(struct sbus_priv *self, struct spi_message *message) { DECLARE_COMPLETION_ONSTACK(done); int status; message->complete = spidev_complete; message->context = &done; spin_lock_irq(&self->lock); if (self->spi == NULL) status = -ESHUTDOWN; else status = spi_async(self->spi, message); spin_unlock_irq(&self->lock); if (status == 0) { wait_for_completion(&done); status = message->status; //if (status == 0) // status = message->actual_length; } return status; } static ssize_t spidev_sync_write_then_write(struct sbus_priv *self, u8 *cmd, size_t cmd_len,const u8 *tx, size_t tx_len) { struct spi_transfer t[2]; struct spi_message m; memset(t, 0, sizeof(t)); t[0].tx_buf = cmd; t[0].len = cmd_len; t[1].tx_buf = tx; t[1].len = tx_len; spi_message_init(&m); spi_message_add_tail(&t[0], &m); spi_message_add_tail(&t[1], &m); return spidev_sync(self, &m); } static ssize_t spidev_sync_write_then_read(struct sbus_priv *self, u8 *cmd, size_t cmd_len, u8 *rx, size_t rx_len) { struct spi_transfer t[2]; struct spi_message m; memset(t, 0, sizeof(t)); t[0].tx_buf = cmd; t[0].len = cmd_len; t[1].rx_buf = rx; t[1].len = rx_len; spi_message_init(&m); spi_message_add_tail(&t[0], &m); spi_message_add_tail(&t[1], &m); return spidev_sync(self, &m); } /* Read-only message with current device setup */ static int atbm_spi_read_data(struct sbus_priv *self, void *rx, size_t rx_len) { ssize_t status = 0; u8 cmdbuff[2] = {0x0b, 0}; status = spidev_sync_write_then_read(self, cmdbuff, sizeof(cmdbuff), rx, rx_len); return status; } /* Write-only message with current device setup */ static int atbm_spi_write_data(struct sbus_priv *self,const void *tx, size_t tx_len) { ssize_t status = 0; u8 cmdbuff[2] = {0x51, 0}; status = spidev_sync_write_then_write(self, cmdbuff, sizeof(cmdbuff), tx, tx_len); return status; } static int atbm_spi_read_status(struct sbus_priv *self, u32 *status, size_t tx_len) { ssize_t ret = 0; u8 cmdbuff[2] = {0x05, 0}; ret = spidev_sync_write_then_read(self, cmdbuff, sizeof(cmdbuff), (u8 *)status, tx_len); return ret; } static int atbm_spi_read_channelflag(struct sbus_priv *self, u32 *channelflag) { ssize_t ret = 0; u32 status = 0; unsigned long flags; ret = atbm_spi_read_status(self, &status, sizeof(status)); if (ret == 0) { spin_lock_irqsave(&self->lock, flags); if ((status & SPI_CHANNEL_FLAG) == self->old_channelFlag) { *channelflag = 0; }else { *channelflag = 1; } spin_unlock_irqrestore(&self->lock, flags); } return ret; } static int atbm_spi_update_channelflag(struct sbus_priv *self) { ssize_t ret = 0; u32 status = 0; unsigned long flags; ret = atbm_spi_read_status(self, &status, sizeof(status)); if (ret == 0) { spin_lock_irqsave(&self->lock, flags); self->old_channelFlag = status & SPI_CHANNEL_FLAG; spin_unlock_irqrestore(&self->lock, flags); } return ret; } static int atbm_spi_read_ready(struct sbus_priv *self, u32 *ready) { ssize_t ret = 0; u32 status = 0; ret = atbm_spi_read_status(self, &status, sizeof(status)); if (ret == 0) { if (status & SPI_STATUS_READY) { *ready = 1; }else { *ready = 0; } } return ret; } static int atbm_spi_reset_cpu(struct sbus_priv *self) { ssize_t ret = 0; u8 cmdbuff[4] = {0xaa, 0, 0, 0xaa}; ret = spidev_sync_write_then_write(self, cmdbuff, 2, &cmdbuff[2], 2); return ret; } static int atbm_spi_shutdown_wlan(struct sbus_priv *self) { ssize_t ret = 0; u8 cmdbuff[4] = {0xaa, 0, 2, 0xac}; ret = spidev_sync_write_then_write(self, cmdbuff, 2, &cmdbuff[2], 2); return ret; } static int atbm_spi_reset_chip(struct sbus_priv *self) { ssize_t ret = 0; u8 cmdbuff[4] = {0xaa, 0, 1, 0xab}; ret = spidev_sync_write_then_write(self, cmdbuff, 2, &cmdbuff[2], 2); return ret; } static int atbm_spi_write_firmware(struct sbus_priv *self,unsigned int addr, const void *src, int count) { ssize_t ret = 0; u32 status = 0; if (addr < DOWNLOAD_DTCM_ADDR) { u8 iccm_cmdbuf[2] = {0x51,0x00}; ret = spidev_sync_write_then_write(self, iccm_cmdbuf, sizeof(iccm_cmdbuf), src, count); } else { //dccm u8 dccm_cmdbuf[2] = {0x56,0x00}; ret = spidev_sync_write_then_write(self, dccm_cmdbuf, sizeof(dccm_cmdbuf), src, count); } if (ret) { return -1; } ret = atbm_spi_read_status(self, &status, sizeof(status)); if ((ret) || (status & SPI_OVERRUN))//overrun { atbm_dbg(ATBM_APOLLO_DBG_ERROR,"[atbm_load_fw]:SPI_STATUS_OVERRUN\n"); ret = -2; } return ret; } static u32 atbm_spi_align_size(struct sbus_priv *self, u32 size) { u32 aligned = size; return aligned; } static int atbm_spi_reset(struct sbus_priv *self) { return 0; } static int atbm_spi_set_block_size(struct sbus_priv *self, u32 size) { return 0; } static void atbm_spi_lock(struct sbus_priv *self) { } static void atbm_spi_unlock(struct sbus_priv *self) { } static int atbm_spi_irq_subscribe(struct sbus_priv *self, sbus_irq_handler handler,void *priv) { int ret = 0; return ret; } static int atbm_spi_irq_unsubscribe(struct sbus_priv *self) { int ret = 0; return ret; } static int atbm_spi_pm(struct sbus_priv *self, bool suspend) { int ret = 0; return ret; } void atbm_spi_read_rdy_start(void) { #ifdef CONFIG_ATBMWIFI_WDT if(g_wtd.wtd_init == 0) return; if(atomic_read(&g_wtd.wtd_term)) return; atomic_set(&g_wtd.wtd_spi_read_ready, 1); wake_up(&g_wtd.wtd_evt_wq); atbm_printk_bus("[atbm_wtd] wakeup.\n"); #endif //CONFIG_ATBMWIFI_WDT } void atbm_spi_read_rdy_end(void) { #ifdef CONFIG_ATBMWIFI_WDT if(g_wtd.wtd_init == 0) return; atomic_set(&g_wtd.wtd_spi_read_ready, 0); #endif //CONFIG_ATBMWIFI_WDT } void atbm_spi_status_rx_ready(struct atbm_common *priv) { int ret = 0; u32 ready =0 ; BUG_ON(!priv->sbus_ops); ret = atbm_spi_read_ready(priv->sbus_priv, &ready); atbm_printk_bus("[wtd] ready %d\n", ready); if ((ready == 1)) { if (atomic_add_return(1, &priv->bh_rx) == 1){ atomic_set(&g_wtd.wtd_spi_read_ready, 0); wake_up(&priv->bh_wq); } } return; } void atbm_wtd_wakeup( struct sbus_priv *self) { #ifdef CONFIG_ATBMWIFI_WDT if(atomic_read(&self->wtd->wtd_term)) return; atomic_set(&g_wtd.wtd_run, 1); atbm_printk_bus( "[atbm_wtd] wakeup.\n"); wake_up(&self->wtd->wtd_evt_wq); #endif //CONFIG_ATBMWIFI_WDT } #ifdef RESET_CHANGE extern struct atbm_common *g_hw_priv; extern int atbm_reset_driver(struct atbm_common *hw_priv); #endif static int atbm_wtd_process(void *arg) { #ifdef CONFIG_ATBMWIFI_WDT int status=0; int term=0; int wtd_run=0; int waittime = 20; int wtd_probe=0; int spi_read_ready = 0; #ifdef RESET_CHANGE int err; #endif atbm_printk_bus("[atbm_wtd]:atbm_wtd_process start++\n"); while(1){ status = wait_event_interruptible(g_wtd.wtd_evt_wq, ({ term = atomic_read(&g_wtd.wtd_term); wtd_run = atomic_read(&g_wtd.wtd_run); spi_read_ready = atomic_read(&g_wtd.wtd_spi_read_ready); (term || wtd_run || spi_read_ready);})); if (status < 0 || term ){ atbm_printk_exit("[atbm_wtd]:1 thread break %d %d\n",status,term); goto __stop; } if (spi_read_ready) { do { atbm_spi_status_rx_ready(g_wtd.core); if (atomic_read(&g_wtd.wtd_term)) { atomic_set(&g_wtd.wtd_spi_read_ready, 0); break; } msleep(1000); }while(atomic_read(&g_wtd.wtd_spi_read_ready)); atbm_printk_exit("[atbm_wtd]:atbm_spi_status_rx_ready end++\n"); continue; } atomic_set(&g_wtd.wtd_run, 0); #ifndef RESET_CHANGE atbm_printk_exit("[atbm_wtd]:atbm_spi_exit++\n"); atbm_spi_exit(); msleep(2000); atbm_printk_exit("[atbm_wtd]:atbm_spi_init++\n"); atbm_spi_init(); atbm_printk_exit("[atbm_wtd]:atbm_spi_init--\n"); //wait 10s for spi init ok, while(waittime-- >0){ msleep(500); term = atomic_read(&g_wtd.wtd_term); if(term) { atbm_printk_exit("[atbm_wtd]:2 thread break %d %d\n",status,term); goto __stop; } wtd_probe = atomic_read(&g_wtd.wtd_probe); if(wtd_probe != 0){ atbm_printk_exit("[atbm_wtd]:wtd_probe(%d) have done\n",wtd_probe); break; } } waittime = 10; //check if spi init ok? wtd_probe = atomic_read(&g_wtd.wtd_probe); //if spi init have probem, need call wtd again if(wtd_probe != 1){ atomic_set(&g_wtd.wtd_run, 1); atbm_printk_exit("[atbm_wtd]:wtd_run again\n"); } #else do { /* *must make sure that g_hw_priv->bh_error is 0 when hmac is *in reset state,but........ */ atbm_hw_priv_dereference()->bh_error = 0; err = atbm_reset_driver(atbm_hw_priv_dereference()); mdelay(5); } while(err == -1); #endif } __stop: while(term){ atbm_printk_bus("[atbm_wtd]:kthread_should_stop\n"); if(kthread_should_stop()){ break; } schedule_timeout_uninterruptible(msecs_to_jiffies(100)); } #endif //CONFIG_ATBMWIFI_WDT return 0; } static void atbm_wtd_init(void) { #ifdef CONFIG_ATBMWIFI_WDT int err = 0; struct sched_param param = { .sched_priority = 1 }; if(g_wtd.wtd_init) return; atbm_printk_init( "[wtd] register.\n"); init_waitqueue_head(&g_wtd.wtd_evt_wq); atomic_set(&g_wtd.wtd_term, 0); g_wtd.wtd_thread = kthread_create(&atbm_wtd_process, &g_wtd, "atbm_wtd"); if (IS_ERR(g_wtd.wtd_thread)) { err = PTR_ERR(g_wtd.wtd_thread); g_wtd.wtd_thread = NULL; } else { WARN_ON(sched_setscheduler(g_wtd.wtd_thread, SCHED_FIFO, ¶m)); #ifdef HAS_PUT_TASK_STRUCT get_task_struct(g_wtd.wtd_thread); #endif wake_up_process(g_wtd.wtd_thread); } g_wtd.wtd_init = 1; #endif //CONFIG_ATBMWIFI_WDT } static void atbm_wtd_exit(void) { #ifdef CONFIG_ATBMWIFI_WDT struct task_struct *thread = g_wtd.wtd_thread; if (WARN_ON(!thread)) return; if(atomic_read(&g_wtd.wtd_term)==0) return; g_wtd.wtd_thread = NULL; atbm_printk_exit("[wtd] unregister.\n"); atomic_add(1, &g_wtd.wtd_term); wake_up(&g_wtd.wtd_evt_wq); kthread_stop(thread); #ifdef HAS_PUT_TASK_STRUCT put_task_struct(thread); #endif g_wtd.wtd_init = 0; #endif //CONFIG_ATBMWIFI_WDT } static struct sbus_ops atbm_spi_sbus_ops = { .sbus_read_data = atbm_spi_read_data, .sbus_write_data = atbm_spi_write_data, .sbus_read_status = atbm_spi_read_status, .sbus_read_ready = atbm_spi_read_ready, .sbus_update_channelflag = atbm_spi_update_channelflag, .sbus_read_channelflag = atbm_spi_read_channelflag, .sbus_reset_cpu = atbm_spi_reset_cpu, .sbus_reset_chip = atbm_spi_reset_chip, .sbus_shutdown_wlan = atbm_spi_shutdown_wlan, .sbus_write_firmware = atbm_spi_write_firmware, .align_size = atbm_spi_align_size, .reset = atbm_spi_reset, .set_block_size = atbm_spi_set_block_size, .lock = atbm_spi_lock, .unlock = atbm_spi_unlock, .irq_unsubscribe = atbm_spi_irq_unsubscribe, .irq_subscribe = atbm_spi_irq_subscribe, .power_mgmt = atbm_spi_pm, }; /* Probe Function to be called by spi stack when device is discovered */ static int atbm_spi_probe(struct spi_device *spi) { struct sbus_priv *self; int status; atbm_dbg(ATBM_APOLLO_DBG_INIT, "Probe called\n"); atomic_set(&g_wtd.wtd_probe, 0); atomic_set(&g_wtd.wtd_spi_read_ready, 0); self = atbm_kzalloc(sizeof(*self), GFP_KERNEL); if (!self) { atbm_dbg(ATBM_APOLLO_DBG_ERROR, "Can't allocate spi sbus_priv."); return -ENOMEM; } spin_lock_init(&self->lock); self->wtd = &g_wtd; self->spi = spi; self->old_channelFlag = 0; status = atbm_core_probe(&atbm_spi_sbus_ops, self, &spi->dev, &g_wtd.core); if (status) { goto err_status; //printk("[atbm_wtd]:set wtd_probe = -1\n"); } self->core = g_wtd.core; atomic_set(&g_wtd.wtd_probe, 1); atbm_printk_init("[atbm_wtd]:set wtd_probe = 1\n"); spi_set_drvdata(spi, self); return 0; err_status: atbm_kfree(self); atomic_set(&g_wtd.wtd_probe, -1); return status; } /* Disconnect Function to be called by spi stack when * device is disconnected */ static int atbm_spi_disconnect(struct spi_device *spi) { struct sbus_priv *self = spi_get_drvdata(spi); if (self) { spin_lock_irq(&self->lock); self->spi = NULL; spi_set_drvdata(spi, NULL); spin_unlock_irq(&self->lock); atomic_set(&g_wtd.wtd_probe, 0); if (self->core) { atbm_core_release(self->core); self->core = NULL; } atbm_kfree(self); } return 0; } static struct spi_driver spi_driver = { .driver = { .name = "ATBMWIFI", .owner = THIS_MODULE, }, .probe = atbm_spi_probe, .remove = (atbm_spi_disconnect), /* NOTE: suspend/resume methods are not necessary here. * We don't do anything except pass the requests to/from * the underlying controller. The refrigerator handles * most issues; the controller driver handles the rest. */ }; static int atbm_reboot_notifier(struct notifier_block *nb, unsigned long action, void *unused) { atbm_printk_exit("atbm_reboot_notifier\n"); atomic_set(&g_wtd.wtd_term, 1); atomic_set(&g_wtd.wtd_run, 0); atbm_spi_exit(); atbm_ieee80211_exit(); atbm_release_firmware(); return NOTIFY_DONE; } /* Probe Function to be called by USB stack when device is discovered */ static struct notifier_block atbm_reboot_nb = { .notifier_call = atbm_reboot_notifier, .priority=1, }; /* Init Module function -> Called by insmod */ static int atbm_spi_init(void) { int ret; ret=driver_build_info(); atbm_wtd_init(); ret=spi_register_driver(&spi_driver); if (ret) goto err_reg; return 0; err_reg: return ret; } /* Called at Driver Unloading */ static void atbm_spi_exit(void) { atbm_wtd_exit(); spi_unregister_driver(&spi_driver); } static int __init apollo_spi_module_init(void) { ieee80211_atbm_mem_int(); ieee80211_atbm_skb_int(); register_reboot_notifier(&atbm_reboot_nb); atbm_init_firmware(); atbm_ieee80211_init(); return atbm_spi_init(); } static void apollo_spi_module_exit(void) { atomic_set(&g_wtd.wtd_term, 1); atomic_set(&g_wtd.wtd_run, 0); atbm_spi_exit(); atbm_ieee80211_exit(); atbm_release_firmware(); unregister_reboot_notifier(&atbm_reboot_nb); ieee80211_atbm_mem_exit(); ieee80211_atbm_skb_exit(); } module_init(apollo_spi_module_init); module_exit(apollo_spi_module_exit);