/* * Copyright (c) 2015 iComm-semi Ltd. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,6,0) #include #else #include #endif #include #include #include #include #include #include #include "ssv_skb.h" #include "dev_tbl.h" #include "dev.h" #include "lib.h" #include "ap.h" #include "regd.h" #include "efuse.h" #include "wow.h" #include "init.h" #include "ssv_skb.h" #include "ssv_cli.h" #include "hw_scan.h" #include #include #ifdef CONFIG_SSV_SUPPORT_ANDROID #include "ssv_pm.h" #endif #if LINUX_VERSION_CODE < KERNEL_VERSION(3,0,0) #include "linux_2_6_35.h" #endif #ifdef CONFIG_SSV6XXX_DEBUGFS #include "ssv6xxx_debugfs.h" #endif MODULE_AUTHOR("iComm-semi, Ltd"); MODULE_DESCRIPTION("Support for SSV6xxx wireless LAN cards."); MODULE_SUPPORTED_DEVICE("SSV6xxx 802.11n WLAN cards"); MODULE_LICENSE("Dual BSD/GPL"); static const struct ieee80211_iface_limit ssv6xxx_p2p_limits[] = { { .max = 2, .types = BIT(NL80211_IFTYPE_STATION), }, { .max = 1, .types = BIT(NL80211_IFTYPE_P2P_GO) | BIT(NL80211_IFTYPE_P2P_CLIENT) | BIT(NL80211_IFTYPE_ADHOC) | BIT(NL80211_IFTYPE_AP), }, }; static const struct ieee80211_iface_combination ssv6xxx_iface_combinations_p2p[] = { { .num_different_channels = 1, .max_interfaces = SSV6200_MAX_VIF, .beacon_int_infra_match = true, .limits = ssv6xxx_p2p_limits, .n_limits = ARRAY_SIZE(ssv6xxx_p2p_limits), }, }; extern struct ssv6xxx_cfg ssv_cfg; static void ssv6xxx_stop_all_running_threads(struct ssv_softc *sc) ; #define HT_CAP_RX_STBC_ONE_STREAM 0x1 void ssv6xxx_set_80211_hw_rate_config(struct ssv_softc *sc) { struct ieee80211_hw *hw = sc->hw; hw->max_rates = 4; hw->max_rate_tries = HW_MAX_RATE_TRIES; } static void ssv6xxx_set_80211_hw_capab(struct ssv_softc *sc) { struct ieee80211_hw *hw=sc->hw; struct ssv_hw *sh=sc->sh; struct ieee80211_sta_ht_cap *ht_info; /* see mac80211.h */ #if LINUX_VERSION_CODE < KERNEL_VERSION(4,2,0) hw->flags = IEEE80211_HW_SIGNAL_DBM; hw->flags |= IEEE80211_HW_HAS_RATE_CONTROL; #ifdef CONFIG_HW_SCAN #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,17,0) hw->flags |= IEEE80211_SINGLE_HW_SCAN_ON_ALL_BANDS; #endif #endif //HW doesn't support, but add this for MAC80211 to help do that. hw->flags |= IEEE80211_HW_MFP_CAPABLE; #else ieee80211_hw_set(hw, SIGNAL_DBM); ieee80211_hw_set(hw, HAS_RATE_CONTROL); #ifdef CONFIG_HW_SCAN ieee80211_hw_set(hw, SINGLE_SCAN_ON_ALL_BANDS); #endif //HW doesn't support, but add this for MAC80211 to help do that. ieee80211_hw_set(hw, MFP_CAPABLE); #endif //hw->flags |= IEEE80211_HW_2GHZ_SHORT_SLOT_INCAPABLE; //hw->flags |= IEEE80211_HW_2GHZ_SHORT_PREAMBLE_INCAPABLE; // Freddie ToDo: Clarify the effect of IEEE80211_HW_SUPPORTS_PS and IEEE80211_HW_PS_NULLFUNC_STACK #ifdef CONFIG_SSV_SUPPORT_ANDROID //hw->flags |= IEEE80211_HW_SUPPORTS_PS; //hw->flags |= IEEE80211_HW_PS_NULLFUNC_STACK; #endif /* Set rate control algorithm if enabled*/ //rate control should always enabled SSV_RC_ALGORITHM(sc); /* set HT capability if hardware suppports HT mode */ ht_info = &sc->sbands[INDEX_80211_BAND_2GHZ].ht_cap; if (sh->cfg.hw_caps & SSV6200_HW_CAP_HT) { if (sh->cfg.hw_caps & SSV6200_HW_CAP_AMPDU_RX) { #if LINUX_VERSION_CODE < KERNEL_VERSION(4,2,0) hw->flags |= IEEE80211_HW_AMPDU_AGGREGATION; hw->flags |= IEEE80211_HW_SPECTRUM_MGMT; #else ieee80211_hw_set(hw, AMPDU_AGGREGATION); ieee80211_hw_set(hw, SPECTRUM_MGMT); #endif } /* SM Power Save disabled */ ht_info->cap |= IEEE80211_HT_CAP_SM_PS; if (sh->cfg.hw_caps & SSV6200_HW_CAP_GF) ht_info->cap |= IEEE80211_HT_CAP_GRN_FLD; if (sh->cfg.hw_caps & SSV6200_HW_CAP_STBC) ht_info->cap |= HT_CAP_RX_STBC_ONE_STREAM << IEEE80211_HT_CAP_RX_STBC_SHIFT; if (sh->cfg.hw_caps & SSV6200_HW_CAP_HT40) ht_info->cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40; if (sh->cfg.hw_caps & SSV6200_HW_CAP_SGI) { ht_info->cap |= IEEE80211_HT_CAP_SGI_20; if (sh->cfg.hw_caps & SSV6200_HW_CAP_HT40) { ht_info->cap |= IEEE80211_HT_CAP_SGI_40; } } if (0 == sh->cfg.ampdu_rx_size_cap) ht_info->ampdu_factor = IEEE80211_HT_MAX_AMPDU_8K; else if (1 == sh->cfg.ampdu_rx_size_cap) ht_info->ampdu_factor = IEEE80211_HT_MAX_AMPDU_16K; else if (2 == sh->cfg.ampdu_rx_size_cap) ht_info->ampdu_factor = IEEE80211_HT_MAX_AMPDU_32K; else ht_info->ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K; ht_info->ampdu_density = IEEE80211_HT_MPDU_DENSITY_8; memset(&ht_info->mcs, 0, sizeof(ht_info->mcs)); ht_info->mcs.rx_mask[0] = 0xff; ht_info->mcs.tx_params |= IEEE80211_HT_MCS_TX_DEFINED; ht_info->mcs.rx_highest = cpu_to_le16(SSV6200_RX_HIGHEST_RATE); ht_info->ht_supported = true; } hw->wiphy->max_scan_ssids = 32; hw->wiphy->max_scan_ie_len = 512; hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION); hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_ADHOC); hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_MONITOR); #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0) if (sh->cfg.hw_caps & SSV6200_HW_CAP_P2P) { hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_P2P_CLIENT); hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_P2P_GO); // BIT(NL80211_IFTYPE_P2P_DEVICE)??? hw->wiphy->iface_combinations = ssv6xxx_iface_combinations_p2p; hw->wiphy->n_iface_combinations = ARRAY_SIZE(ssv6xxx_iface_combinations_p2p); #if LINUX_VERSION_CODE < KERNEL_VERSION(3,5,0) hw->wiphy->flags |= WIPHY_FLAG_ENFORCE_COMBINATIONS; #endif//3.5.0 } #endif #if LINUX_VERSION_CODE > KERNEL_VERSION(3,5,0) hw->wiphy->flags |= WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL; #endif if (sh->cfg.hw_caps & SSV6200_HW_CAP_AP){ hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_AP); #if LINUX_VERSION_CODE > KERNEL_VERSION(3,1,0) hw->wiphy->flags |= WIPHY_FLAG_AP_UAPSD; #endif } #if LINUX_VERSION_CODE > KERNEL_VERSION(3,0,8) if (sh->cfg.hw_caps & SSV6200_HW_CAP_TDLS){ hw->wiphy->flags |= WIPHY_FLAG_SUPPORTS_TDLS; hw->wiphy->flags |= WIPHY_FLAG_TDLS_EXTERNAL_SETUP; printk("TDLS function enabled in sta.cfg\n"); } #endif hw->wiphy->flags |= WIPHY_FLAG_IBSS_RSN; hw->queues = 4; #if LINUX_VERSION_CODE < KERNEL_VERSION(3,14,0) hw->channel_change_time = 5000; #endif hw->max_listen_interval = 1; //set hw support max rate and rate retry SSV_SET_80211HW_RATE_CONFIG(sc); hw->extra_tx_headroom = TXPB_OFFSET; hw->extra_tx_headroom += SSV_SKB_info_size; if (sh->cfg.hw_caps & SSV6200_HW_CAP_2GHZ) { hw->wiphy->bands[INDEX_80211_BAND_2GHZ] = &sc->sbands[INDEX_80211_BAND_2GHZ]; } if (sh->cfg.hw_caps & SSV6200_HW_CAP_5GHZ) { memcpy(&sc->sbands[INDEX_80211_BAND_5GHZ].ht_cap, ht_info, sizeof(struct ieee80211_sta_ht_cap)); hw->wiphy->bands[INDEX_80211_BAND_5GHZ] = &sc->sbands[INDEX_80211_BAND_5GHZ]; } // SET_IEEE80211_PERM_ADDR(hw, sh->cfg.maddr); #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0) /* Set rate control algorithm if enabled*/ /* max rx aggr size will affect buffer size in addba response and affect hw BA window size. For Cabrio, hw ba window size must be 64 otherwise, it will get compatibility issue */ /*if (sh->cfg.hw_caps & SSV6200_HW_CAP_AMPDU_TX) #ifdef PREFER_RX hw->max_rx_aggregation_subframes = 64; //SSV_AMPDU_TX_SUBFRAME_NUM; #else // PREFER_RX hw->max_rx_aggregation_subframes = 16; //SSV_AMPDU_RX_SUBFRAME_NUM; #endif // PREFER_RX else hw->max_rx_aggregation_subframes = 12; //SSV_AMPDU_RX_SUBFRAME_NUM;*/ hw->max_rx_aggregation_subframes = sh->cfg.max_rx_aggr_size; hw->max_tx_aggregation_subframes = 64; //SSV_AMPDU_TX_SUBFRAME_NUM; #endif hw->sta_data_size = sizeof(struct ssv_sta_priv_data); /* drv_priv sizeof of struct ieee80211_sta */ hw->vif_data_size = sizeof(struct ssv_vif_priv_data); /* drv_priv sizeof of struct ieee80211_vif */ // Freddie ToDo: Get device configuration from device ID. /* Assign all mac addresses to wiphy */ memcpy(sh->maddr[0].addr, &sh->cfg.maddr[0][0], ETH_ALEN); hw->wiphy->addresses = sh->maddr; hw->wiphy->n_addresses = 1; // Freddie ToDo: // Add MAC address from hardware capability of # interfaces not P2P. // Use mask instead of MAC address if (sh->cfg.hw_caps & SSV6200_HW_CAP_P2P) { int i; for (i = 1; i < SSV6200_MAX_HW_MAC_ADDR; i++) { memcpy(sh->maddr[i].addr, sh->maddr[i-1].addr, ETH_ALEN); sh->maddr[i].addr[5]++; hw->wiphy->n_addresses++; } } // If second MAC address exists in configuration, enable dual interface. if (!is_zero_ether_addr(sh->cfg.maddr[1])) { memcpy(sh->maddr[1].addr, sh->cfg.maddr[1], ETH_ALEN); if (hw->wiphy->n_addresses < 2) hw->wiphy->n_addresses = 2; } #ifdef CONFIG_PM ssv6xxx_attach_wow(sc); #endif if (sh->cfg.hw_caps & SSV6200_HW_CAP_REPORT_TX_ACK) { #if LINUX_VERSION_CODE < KERNEL_VERSION(4,2,0) hw->flags |= IEEE80211_HW_REPORTS_TX_ACK_STATUS; #else ieee80211_hw_set(hw, REPORTS_TX_ACK_STATUS); #endif } } static void ssv6xxx_preload_sw_cipher(void) { return; } #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)) static inline void house_keeping_timer_hdl(struct timer_list *in_timer) { struct ssv_timer_list *ptimer = from_timer(ptimer, in_timer, timer); ptimer->function((unsigned long)ptimer->arg); } #endif static int ssv6xxx_init_softc(struct ssv_softc *sc) { int ret = 0; mutex_init(&sc->mutex); mutex_init(&sc->mem_mutex); mutex_init(&sc->ampdu_stop_mutex); sc->config_wq= create_singlethread_workqueue("ssv6xxx_cong_wq"); INIT_WORK(&sc->hw_restart_work, ssv6xxx_restart_hw); INIT_WORK(&sc->beacon_miss_work, ssv6xxx_beacon_miss_work); INIT_WORK(&sc->bcast_start_work, ssv6200_bcast_start_work); INIT_DELAYED_WORK(&sc->bcast_stop_work, ssv6200_bcast_stop_work); INIT_DELAYED_WORK(&sc->bcast_tx_work, ssv6200_bcast_tx_work); INIT_WORK(&sc->set_ampdu_rx_add_work, ssv6xxx_set_ampdu_rx_add_work); INIT_WORK(&sc->set_ampdu_rx_del_work, ssv6xxx_set_ampdu_rx_del_work); #ifdef CONFIG_SSV_SUPPORT_ANDROID #ifdef CONFIG_HAS_EARLYSUSPEND sc->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 20; sc->early_suspend.suspend = ssv6xxx_early_suspend; sc->early_suspend.resume = ssv6xxx_late_resume; register_early_suspend(&sc->early_suspend); #endif //CONFIG_HAS_EARLYSUSPEND ssv_wakelock_init(sc); #endif //use to send mcast frame. // init_timer(&sc->bcast_timeout); // sc->bcast_timeout.data = (unsigned long)sc; // sc->bcast_timeout.function = ssv6200_bcast_timer; /* By default, we apply staion decion table. */ sc->mac_deci_tbl = sta_deci_tbl; /** * Initialize tx: * Associate WMM AC queue to each hardward tx queue. * For hardware queue, queue 0 has the lowest priority. */ memset((void *)&sc->tx, 0, sizeof(struct ssv_tx)); sc->tx.hw_txqid[WMM_AC_VO] = 3; sc->tx.ac_txqid[3] = WMM_AC_VO; sc->tx.hw_txqid[WMM_AC_VI] = 2; sc->tx.ac_txqid[2] = WMM_AC_VI; sc->tx.hw_txqid[WMM_AC_BE] = 1; sc->tx.ac_txqid[1] = WMM_AC_BE; sc->tx.hw_txqid[WMM_AC_BK] = 0; sc->tx.ac_txqid[0] = WMM_AC_BK; /** * Initialize rx: */ memset((void *)&sc->rx, 0, sizeof(struct ssv_rx)); /* Initialize broadcast queue */ memset(&sc->bcast_txq, 0, sizeof(struct ssv6xxx_bcast_txq)); spin_lock_init(&sc->bcast_txq.txq_lock); skb_queue_head_init(&sc->bcast_txq.qhead); /* Initialize power saver spin lock */ spin_lock_init(&sc->ps_state_lock); /* Initialize tx_pkt_run_no lock */ spin_lock_init(&sc->tx_pkt_run_no_lock); /* Initialize sta_info protection semaphore */ init_rwsem(&sc->sta_info_sem); /* Initialize channels & rates */ if (ssv6xxx_update_hw_channel(sc) < 0) goto err_create_channel_list; sc->cur_channel = NULL; sc->hw_chan = (-1); // Wait queue for TX/RX skb_queue_head_init(&sc->tx_done_q); skb_queue_head_init(&sc->tx_ack_ctl_q); // Wait queue for TX/RX init_waitqueue_head(&sc->rx_wait_q); skb_queue_head_init(&sc->rx_skb_q); sc->rx_task = kthread_run(ssv6xxx_rx_task, sc, "ssv6xxx_rx_task"); if (SSV_NEED_SW_CIPHER(sc->sh)){ ssv6xxx_preload_sw_cipher(); } init_waitqueue_head(&sc->fw_wait_q); /* Enable HWIF log*/ sc->log_ctrl = LOG_HWIF; sc->sh->priv->dbg_control = true; sc->cmd_data.log_to_ram = false; sc->cmd_data.dbg_log.size = 0; sc->cmd_data.dbg_log.totalsize = 0; sc->cmd_data.dbg_log.data = NULL; /* House keeping */ sc->house_keeping.function = ssv6xxx_house_keeping; sc->house_keeping.arg = (void *)sc; #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)) timer_setup(&sc->house_keeping.timer, house_keeping_timer_hdl, 0); sc->house_keeping.timer.expires = jiffies + msecs_to_jiffies(HOUSE_KEEPING_TIMEOUT); #else init_timer(&sc->house_keeping.timer); sc->house_keeping.timer.expires = jiffies + msecs_to_jiffies(HOUSE_KEEPING_TIMEOUT); sc->house_keeping.timer.function = ssv6xxx_house_keeping; sc->house_keeping.timer.data = (unsigned long)sc; #endif sc->house_keeping_wq= create_singlethread_workqueue("ssv6xxx_house_keeping_wq"); INIT_WORK(&sc->check_fw_status_work, ssv6xxx_check_fw_status_process); INIT_WORK(&sc->mib_edca_work, ssv6xxx_mib_edca_process); INIT_WORK(&sc->tx_poll_work, ssv6xxx_tx_poll_process); INIT_WORK(&sc->flowctl_work, ssv6xxx_flowctl_process); #ifdef CONFIG_ENABLE_HOST_THERMAL INIT_WORK(&sc->thermal_monitor_work, ssv6xxx_thermal_monitor_process); #endif sc->mac80211_dev_started = false; add_timer(&sc->house_keeping.timer); /* tx done handler */ sc->tx_done_wq= create_singlethread_workqueue("ssv6xxx_tx_done_wq"); INIT_WORK(&sc->tx_done_work, ssv6xxx_tx_done_process); /* hw scan handler */ sc->scan_wq= create_singlethread_workqueue("ssv6xxx_scan_wq"); INIT_DELAYED_WORK(&sc->scan_work, ssv6xxx_scan_process); INIT_DELAYED_WORK(&sc->scan_ignore_work, ssv6xxx_scan_ignore_process); /* Directly ack flow control */ sc->sc_flags |= SC_OP_DIRECTLY_ACK; atomic_set(&sc->tx_frame, 0); // init flag of force disable directly ack tx frame sc->force_disable_directly_ack_tx = false; init_completion(&sc->wakeup_done); init_completion(&sc->hold_on3); return ret; err_create_channel_list: return -ENOMEM; } static int ssv6xxx_deinit_softc(struct ssv_softc *sc) { void *channels; struct sk_buff* skb; u8 remain_size; printk("%s():\n", __FUNCTION__); if (sc->sh->cfg.hw_caps & SSV6200_HW_CAP_2GHZ) { channels = sc->sbands[INDEX_80211_BAND_2GHZ].channels; kfree(channels); } if (sc->sh->cfg.hw_caps & SSV6200_HW_CAP_5GHZ) { channels = sc->sbands[INDEX_80211_BAND_5GHZ].channels; kfree(channels); } #ifdef CONFIG_SSV_SUPPORT_ANDROID #ifdef CONFIG_HAS_EARLYSUSPEND unregister_early_suspend(&sc->early_suspend); #endif //CONFIG_HAS_EARLYSUSPEND ssv_wakelock_destroy(sc); #endif /* Release broadcast frames. */ do{ skb = ssv6200_bcast_dequeue(&sc->bcast_txq, &remain_size); if(skb) ssv6xxx_txbuf_free_skb(skb, (void*)sc);//dev_kfree_skb_any(skb); else break; }while(remain_size); printk("%s(): Clean Tx Ack_Ctl queues.\n", __func__); while ((skb = skb_dequeue(&sc->tx_ack_ctl_q)) != NULL) { dev_kfree_skb_any(skb); } printk("%s(): Clean RX queues %d.\n", __func__, skb_queue_len(&sc->rx_skb_q)); while ((skb = skb_dequeue(&sc->rx_skb_q)) != NULL) { dev_kfree_skb_any(skb); } if (sc->cmd_data.dbg_log.data) kfree(sc->cmd_data.dbg_log.data); destroy_workqueue(sc->config_wq); // remove house keeping del_timer_sync(&sc->house_keeping.timer); destroy_workqueue(sc->house_keeping_wq); // remove tx done destroy_workqueue(sc->tx_done_wq); // remove scan destroy_workqueue(sc->scan_wq); return 0; } static void ssv6xxx_deinit_hwsh(struct ssv_softc *sc) { struct ssv_hw *sh = sc->sh; if (sh->page_count) kfree(sh->page_count); kfree(sc->sh); } extern char *cfgfirmwarepath ; int ssv6xxx_load_firmware(struct ssv_hw *sh) { int ret=0; u8 firmware_name[SSV_FIRMWARE_MAX] = ""; u8 temp_path[SSV_FIRMWARE_PATH_MAX] = ""; if (sh->cfg.external_firmware_name[0] != 0x00) { printk(KERN_INFO "Forced to use firmware \"%s\".\n", sh->cfg.external_firmware_name); strncpy(firmware_name, sh->cfg.external_firmware_name, SSV_FIRMWARE_MAX-1); } else { SSV_GET_FW_NAME(sh, firmware_name); printk(KERN_INFO "Using firmware \"%s\".\n", firmware_name); } if (firmware_name[0] == 0x00) { printk(KERN_INFO "Not match correct CHIP identity\n"); return -1; } if (cfgfirmwarepath != NULL) { snprintf(temp_path, SSV_FIRMWARE_PATH_MAX, "%s%s", cfgfirmwarepath, firmware_name); // Open firmware by open file api ret = HAL_LOAD_FW(sh,temp_path, 1); printk(KERN_INFO "Using firmware at %s\n", temp_path); } else if (sh->cfg.firmware_path[0] != 0x00) { snprintf(temp_path, SSV_FIRMWARE_PATH_MAX, "%s%s", sh->cfg.firmware_path, firmware_name); // Open firmware by open file api ret = HAL_LOAD_FW(sh,temp_path, 1); printk(KERN_INFO "Using firmware at %s\n", temp_path); } else { //Default firmware //printk(KERN_INFO "Firmware name =%s\n", firmware_name); // Open firmware by request api ret = HAL_LOAD_FW(sh,firmware_name, 0); } return ret; } int ssv6xxx_init_mac(struct ssv_hw *sh) { struct ssv_softc *sc=sh->sc; int ret=0; //----------------------------------------------------------------------------------------------------------------------------------------- printk(KERN_INFO "SVN version %d\n", ssv_root_version); printk(KERN_INFO "SVN ROOT URL %s \n", SSV_ROOT_URl); printk(KERN_INFO "COMPILER HOST %s \n", COMPILERHOST); printk(KERN_INFO "COMPILER DATE %s \n", COMPILERDATE); printk(KERN_INFO "COMPILER OS %s \n", COMPILEROS); printk(KERN_INFO "COMPILER OS ARCH %s \n", COMPILEROSARCH); //----------------------------------------------------------------------------------------------------------------------------------------- //for power saving if(sc->ps_status == PWRSV_ENABLE){ #ifdef CONFIG_SSV_SUPPORT_ANDROID printk(KERN_INFO "%s: wifi Alive lock timeout after 3 secs!\n",__FUNCTION__); { ssv_wake_timeout(sc, 3); printk(KERN_INFO "wifi Alive lock!\n"); } #endif if (USE_MAC80211_RX(sh)){ HAL_SET_RX_FLOW(sh, RX_DATA_FLOW, RX_HCI); } else { HAL_SET_RX_FLOW(sh, RX_DATA_FLOW, RX_CIPHER_MIC_HCI); } HAL_SET_RX_FLOW(sh, RX_MGMT_FLOW, RX_HCI); SSV_SET_RX_CTRL_FLOW(sh); //ACTION_DO_NOTHING, FRAME_ACCEPT 0x11F8 /* 0 001000 111111 00 1 */ //Beacon HAL_UPDATE_DECISION_TABLE_6(sc->sh, sc->mac_deci_tbl[6]); return ret; } /* force RX reset to avoid pbuf allocating out-of-order address */ SSV_PHY_ENABLE(sh, false); // init mac hw ret = HAL_INIT_MAC(sh); if (ret) { printk("HAL INIT MAC FAIL\n"); goto exit; } SSV_PLL_CHK(sh); SSV_PHY_ENABLE(sh, true); exit: return ret; } void ssv6xxx_deinit_mac(struct ssv_softc *sc) { return; } void inline ssv6xxx_deinit_hw(struct ssv_softc *sc) { printk("%s(): \n", __FUNCTION__); ssv6xxx_deinit_mac(sc); /* it cannot detect usb if insmod/rmmod usb-core modules */ #if 0 HAL_RESET_CPU(sc->sh); // Set ILM/DLM back for turismo C0/D0 HAL_SET_SRAM_MODE(sc->sh, SRAM_MODE_ILM_64K_DLM_128K); // TODO, it should verify why to set this register //HAL_DETACH_USB_HCI(sc->sh); // disable power domain ON3 SSV_SET_ON3_ENABLE(sc->sh, false); #endif } static void ssv6xxx_update_tx_waitnum_opertaion(struct ssv_softc *sc, u32 val) { struct sk_buff *skb = NULL; struct cfg_host_cmd *host_cmd = NULL; int ret = 0; skb = dev_alloc_skb(HOST_CMD_HDR_LEN + sizeof(u32)); if (skb == NULL) { printk("%s(): Fail to alloc cmd buffer.\n", __FUNCTION__); return; } skb_put(skb, HOST_CMD_HDR_LEN + sizeof(u32)); host_cmd = (struct cfg_host_cmd *)skb->data; memset(host_cmd, 0x0, sizeof(struct cfg_host_cmd)+sizeof(u32)); host_cmd->c_type = HOST_CMD; host_cmd->h_cmd = (u8)SSV6XXX_HOST_CMD_TX_OPS; host_cmd->sub_h_cmd = (u32)SSV6XXX_TX_CMD_WAIT_NUM; host_cmd->un.dat32[0] = val; host_cmd->blocking_seq_no = (((u16)SSV6XXX_HOST_CMD_TX_OPS << 16)|(u16)SSV6XXX_TX_CMD_WAIT_NUM); host_cmd->len = HOST_CMD_HDR_LEN + sizeof(u32); ret = HCI_SEND_CMD(sc->sh, skb); if (ret) printk("%s(): Fail to send tx operation\n", __FUNCTION__); } static void ssv6xxx_update_tx_checkhwqnum_opertaion(struct ssv_softc *sc, u32 val) { struct sk_buff *skb = NULL; struct cfg_host_cmd *host_cmd = NULL; int ret = 0; skb = dev_alloc_skb(HOST_CMD_HDR_LEN + sizeof(u32)); if (skb == NULL) { printk("%s(): Fail to alloc cmd buffer.\n", __FUNCTION__); return; } skb_put(skb, HOST_CMD_HDR_LEN + sizeof(u32)); host_cmd = (struct cfg_host_cmd *)skb->data; memset(host_cmd, 0x0, sizeof(struct cfg_host_cmd)+sizeof(u32)); host_cmd->c_type = HOST_CMD; host_cmd->h_cmd = (u8)SSV6XXX_HOST_CMD_TX_OPS; host_cmd->sub_h_cmd = (u32)SSV6XXX_TX_CMD_CHECK_HWQ_NUM; host_cmd->un.dat32[0] = val; host_cmd->blocking_seq_no = (((u16)SSV6XXX_HOST_CMD_TX_OPS << 16)|(u16)SSV6XXX_TX_CMD_CHECK_HWQ_NUM); host_cmd->len = HOST_CMD_HDR_LEN + sizeof(u32); ret = HCI_SEND_CMD(sc->sh, skb); if (ret) printk("%s(): Fail to send tx operation\n", __FUNCTION__); } static void ssv6xxx_update_tx_duration_operation(struct ssv_softc *sc, u32 duration, u32 period) { struct sk_buff *skb = NULL; struct cfg_host_cmd *host_cmd = NULL; struct ssv_tx_duration *ptr = NULL; int ret = 0; skb = dev_alloc_skb(HOST_CMD_HDR_LEN + sizeof(struct ssv_tx_duration)); if (skb == NULL) { printk("%s(): Fail to alloc cmd buffer.\n", __FUNCTION__); return; } skb_put(skb, HOST_CMD_HDR_LEN + sizeof(struct ssv_tx_duration)); host_cmd = (struct cfg_host_cmd *)skb->data; memset(host_cmd, 0x0, sizeof(struct cfg_host_cmd)+sizeof(struct ssv_tx_duration)); host_cmd->c_type = HOST_CMD; host_cmd->h_cmd = (u8)SSV6XXX_HOST_CMD_TX_OPS; host_cmd->sub_h_cmd = (u32)SSV6XXX_TX_CMD_DURATION; host_cmd->blocking_seq_no = (((u16)SSV6XXX_HOST_CMD_TX_OPS << 16)|(u16)SSV6XXX_TX_CMD_DURATION); host_cmd->len = HOST_CMD_HDR_LEN + sizeof(struct ssv_tx_duration); ptr = (struct ssv_tx_duration *)host_cmd->un.dat8; ptr->duration = (u16)duration; ptr->period = (u16)period; ret = HCI_SEND_CMD(sc->sh, skb); if (ret) printk("%s(): Fail to send tx operation\n", __FUNCTION__); } static int ssv6xxx_init_hw(struct ssv_hw *sh) { struct ssv_softc *sc = sh->sc; u32 dev_type = HCI_DEVICE_TYPE(sh->hci.hci_ctrl); int ret = 0; u32 regval = 0; ssv_cabrio_reg *rf_tbl, *phy_tbl ; static u8 set_patch = 0; /* ssv6200 hardware parameter settings. */ sh->tx_desc_len = HAL_GET_TX_DESC_SIZE(sh); sh->rx_desc_len = HAL_GET_RX_DESC_SIZE(sh); sh->rx_pinfo_pad = 0x04; sh->rx_mode = RX_NORMAL_MODE; HAL_SET_XTAL_CLK(sh); HAL_RESET_CPU(sh); // disable power domain ON3 SSV_SET_ON3_ENABLE(sh, false); // enable power domain ON3 SSV_SET_ON3_ENABLE(sh, true); // wait USB_ROM_CODE_READY HAL_WAIT_USB_ROM_READY(sh); if (dev_type == SSV_HWIF_INTERFACE_USB) SSV_DISABLE_USB_ACC(sc, 0x4); if ((ret = HAL_RESET_HW_MAC(sh)) != 0) { printk(KERN_ERR "Fail to init hw procedure\n"); goto err; } if (dev_type == SSV_HWIF_INTERFACE_USB) SSV_ENABLE_USB_ACC(sc, 0x4); if ((ret = HAL_INIT_HCI_RX_AGGR(sh)) != 0) { printk(KERN_ERR "Fail to init hw procedure\n"); goto err; } // update tx/rx page if (dev_type == SSV_HWIF_INTERFACE_USB) { if ((sc->sh->cfg.usb_hw_resource & USB_HW_RESOURCE_CHK_FORCE_OFF) == 0) { sc->sh->cfg.usb_hw_resource = ( USB_HW_RESOURCE_CHK_TXID | USB_HW_RESOURCE_CHK_TXPAGE); } else { sc->sh->cfg.usb_hw_resource &= ~( USB_HW_RESOURCE_CHK_TXID | USB_HW_RESOURCE_CHK_TXPAGE); } } HAL_UPDATE_PAGE_ID(sh); mdelay(10); if ((ret = ssv6xxx_load_firmware(sh)) != 0) { printk(KERN_ERR "Fail to load firmware\n"); goto err; } HAL_GET_FW_VERSION(sh, ®val); if (regval == FIRWARE_NOT_MATCH_CODE){ printk(KERN_INFO "Firmware check CHIP ID fail[0x%08x]!!\n",regval); goto err; } else { printk(KERN_INFO "Firmware version %d\n", regval); if (regval != ssv_firmware_version) { if (sh->cfg.ignore_firmware_version == 0) { printk(KERN_INFO "Firmware version mapping not match[0x%08x]!!\n",regval); printk(KERN_INFO "It's should be [0x%08x]!!\n",ssv_firmware_version); goto err; } else printk(KERN_INFO "Force ignore_firmware_version\n"); } } // Set FW tx operation if (0 != sc->sh->cfg.fw_tx_waitnum) ssv6xxx_update_tx_waitnum_opertaion(sc, sc->sh->cfg.fw_tx_waitnum); if (0 != sc->sh->cfg.fw_tx_chkhwqnum) ssv6xxx_update_tx_checkhwqnum_opertaion(sc, sc->sh->cfg.fw_tx_chkhwqnum); if (0 != sc->sh->cfg.fw_tx_duration) ssv6xxx_update_tx_duration_operation(sc, sc->sh->cfg.fw_tx_duration, sc->sh->cfg.fw_tx_duration_period); HAL_INIT_GPIO_CFG(sh); HAL_LOAD_PHY_TABLE(sh, &phy_tbl); HAL_LOAD_RF_TABLE(sh, &rf_tbl); //write phy RF table and initial PLL if ((ret = HAL_SET_PLL_PHY_RF(sh, rf_tbl, phy_tbl)) != 0) { printk(KERN_ERR "Fail to initial PLL\n"); goto err; } if (set_patch == 0) { set_patch = 1; printk("patch[%08x => %08x]\n", 0xccb0e134, 0x00100010); SMAC_REG_WRITE(sc->sh, 0xccb0e134, 0x00100010); } //Switch clock to PLL output of RF if ((ret = HAL_CHG_CLK_SRC(sh)) != 0) { printk(KERN_ERR "Fail to CLK SRC\n"); goto err; } //PHY and security table if ((ret = HAL_INI_HW_SEC_PHY_TABLE(sh->sc)) != 0) { printk(KERN_ERR "Fail to init security table\n"); goto err; } /** * The ordering of PHY/RF default register setting, IQ Calibration and * channel calibration is (from bernie's suggestion) * * 1. channel calibration (to lock on a channel) * 2. IQ calibration * 3. set default PHY registers * 4. set default RF registers * 5. channel calibration ................... */ { struct ieee80211_channel chan, *pchan; memset(&chan, 0 , sizeof( struct ieee80211_channel)); chan.hw_value = sh->cfg.def_chan; pchan = &chan; if ( ret == 0){ HAL_SET_CHANNEL_CHECK(sh->sc, pchan, NL80211_CHAN_HT20, false, ret); } } // enable all phy sub-mode, without phy mode enabled. // phy mode should be enable at init mac. HAL_SET_PHY_MODE(sh, true); err: return ret; } static void ssv6xxx_clear_sta_info(struct ssv_softc *sc) { struct ssv_sta_priv_data *sta_priv_dat = NULL; struct ssv_sta_info *sta_info = NULL; struct ssv_vif_info *vif_info = NULL; struct ieee80211_vif *vif = NULL; s8 hw_wsid = -1; int i = 0; down_write(&sc->sta_info_sem); for(i = 0; i < SSV_NUM_STA; i++) { sta_info = &sc->sta_info[i]; if(sta_info->sta) { hw_wsid = sta_info->hw_wsid; HCI_TXQ_LOCK_BY_STA(sc->sh, hw_wsid); HCI_TX_PAUSE_BY_STA(sc->sh, hw_wsid); HCI_TXQ_FLUSH_BY_STA(sc->sh, hw_wsid); sta_priv_dat = (struct ssv_sta_priv_data *)sta_info->sta->drv_priv; memset((void *)sta_info, 0, sizeof(*sta_info)); sta_priv_dat->sta_idx = -1; list_del(&sta_priv_dat->list); HCI_TX_RESUME_BY_STA(sc->sh, hw_wsid); HCI_TXQ_UNLOCK_BY_STA(sc->sh, hw_wsid); } } up_write(&sc->sta_info_sem); for(i = 0; i < SSV_NUM_VIF; i++) { vif_info = &sc->vif_info[i]; vif = vif_info->vif; if(vif) { #ifdef CONFIG_SSV6XXX_DEBUGFS ssv6xxx_debugfs_remove_interface(sc, vif); #endif if (vif->type == NL80211_IFTYPE_AP) { /* In normal ap mode, release bcast frame and stop worker */ //ssv6200_release_bcast_frame_res(sc, vif); //printk("Config Q4 to normal Q \n"); /* Relase skb of beacon frame */ ssv6xxx_beacon_release(sc); sc->ap_vif = NULL; #ifdef CONFIG_SSV_SUPPORT_ANDROID if(vif->p2p == 0) { ssv_wake_unlock(sc); printk(KERN_INFO "AP mode destroy wifi_alive_lock\n"); } #endif } memset(vif_info, 0, sizeof(*vif_info)); if(sc->nvif != 0) { sc->nvif--; } } } sc->isAssoc = false; } void ssv6xxx_restart_hw(struct work_struct *work) { struct ssv_softc *sc = container_of(work, struct ssv_softc, hw_restart_work); int i = 0; printk("**************************\n"); printk("*** Software MAC reset ***\n"); printk("**************************\n"); if (sc->sc_flags & SC_OP_HW_RESET) { printk("Reset is running.\n"); return; } sc->sc_flags |= SC_OP_HW_RESET; sc->restart_counter++; HCI_IGNORE_CMD(sc->sh, true); // wait for scan completion while (sc->bScanning) { if(sc->hw_scan_start) { ssv6xxx_cancel_hw_scan(sc); } mdelay(100); if (++i > 10000) return; } HCI_STOP(sc->sh); // lock for all changes to network configuration rtnl_lock(); // lock for ssv changes mutex_lock(&sc->mutex); printk("%s() start\n", __FUNCTION__); SSV_BEACON_LOSS_DISABLE(sc->sh); //Disable PHY SSV_PHY_ENABLE(sc->sh, false); mdelay(50); //Delay for wait no packet in firmware. SSV_SAVE_HW_STATUS(sc); SSV_RESET_SYSPLF(sc->sh); // Ep0 -> Ep1 HCI_REVERSE_CONFIG_DEVICE(sc->sh->hci.hci_ctrl); udelay(50); HAL_RESET_CPU(sc->sh); ssv6xxx_clear_sta_info(sc); HCI_IGNORE_CMD(sc->sh, false); ssv6xxx_init_hw(sc->sh); mutex_unlock(&sc->mutex); rtnl_unlock(); ieee80211_restart_hw(sc->hw); if (sc->isAssoc) { SSV_BEACON_LOSS_ENABLE(sc->sh); } // restart hw sc->sc_flags &= (~(SC_OP_HW_RESET)); } static void ssv6xxx_check_mac2(struct ssv_hw *sh) { const u8 addr_mask[6]={0xfd, 0xff, 0xff, 0xff, 0xff, 0xfc}; u8 i; bool invalid = false; for ( i=0; i<6; i++) { if ((ssv_cfg.maddr[0][i] & addr_mask[i]) != (ssv_cfg.maddr[1][i] & addr_mask[i])){ invalid = true; printk (" i %d , mac1[i] %x, mac2[i] %x, mask %x \n",i, ssv_cfg.maddr[0][i] ,ssv_cfg.maddr[1][i],addr_mask[i]); break; } } if (invalid){ memcpy(&ssv_cfg.maddr[1][0], &ssv_cfg.maddr[0][0], 6); ssv_cfg.maddr[1][5] ^= 0x01; if (ssv_cfg.maddr[1][5] < ssv_cfg.maddr[0][5]){ //swap mac1 mac2, let lower number be MAC1 u8 temp; temp = ssv_cfg.maddr[0][5]; ssv_cfg.maddr[0][5] = ssv_cfg.maddr[1][5]; ssv_cfg.maddr[1][5] = temp; sh->cfg.maddr[0][5] = ssv_cfg.maddr[0][5]; } printk("MAC 2 address invalid!!\n" ); printk("After modification, MAC1 %pM, MAC2 %pM\n",ssv_cfg.maddr[0], ssv_cfg.maddr[1]); } } #ifdef CONFIG_ENABLE_HOST_THERMAL static char *m_strtok(char **string, const char *delimiters, char *tokdelim) { unsigned char *str; unsigned long map[8]; int count; char *nextoken; if (tokdelim != NULL) { /* Prime the token delimiter */ *tokdelim = '\0'; } /* Clear control map */ for (count = 0; count < 8; count++) { map[count] = 0; } /* Set bits in delimiter table */ do { map[*delimiters >> 5] |= (1 << (*delimiters & 31)); } while (*delimiters++); str = (unsigned char*)*string; /* Find beginning of token (skip over leading delimiters). Note that * there is no token iff this loop sets str to point to the terminal * null (*str == '\0') */ while (((map[*str >> 5] & (1 << (*str & 31))) && *str) || (*str == ' ')) { str++; } nextoken = (char*)str; /* Find the end of the token. If it is not the end of the string, * put a null there. */ for (; *str; str++) { if (map[*str >> 5] & (1 << (*str & 31))) { if (tokdelim != NULL) { *tokdelim = *str; } *str++ = '\0'; break; } } *string = (char*)str; /* Determine if a token has been found. */ if (nextoken == (char *) str) { return NULL; } else { return nextoken; } } static int ssv6xxx_read_host_thermal_compensation_table(struct ssv_hw *sh) { // char str[]="3,20,2,0xccb0b000:0x10001,0xccb0b000:0x10002,30,2, 0xccb0b004:0x10003,0xccb0b004:0x10004"; char *str_table = sh->cfg.temp_compensation_table; char *endp = 0; char *p_tmp = 0; int i = 0, j = 0, temp_range_count = 0, reg_count = 0, temp_value = 0; u32 address = 0, value = 0, ret = 0; if (sh->cfg.disable_host_thermal) { return 0; } printk("str_table = %s\n", str_table); memset(&sh->htc_config,0,sizeof(struct hw_temp_compensation_config)); p_tmp = m_strtok(&str_table,",",0); if (p_tmp != NULL) { printk("read temp range config\n"); // TEMP COUNT temp_range_count = simple_strtoul(p_tmp, &endp, 0); if (temp_range_count > MAX_LENTH_OF_TABLE_COMPENSATION) { printk("temp_range_count(%d) is too big\n", temp_range_count); } else { sh->htc_config.exist = 1; sh->htc_config.hw_temp_boundary_levels = temp_range_count; printk("temp_range_count [%d]\n", temp_range_count); for ( i = 1; i <= sh->htc_config.hw_temp_boundary_levels; i++) { // TEMP VAULE p_tmp = m_strtok(&str_table, ",", 0); //printk("1. p_tmp = %s\n", p_tmp); if(p_tmp!=NULL) { temp_value = simple_strtol(p_tmp, &endp, 0); if (((temp_value < -30) && (temp_value != -0xff)) || (temp_value > 120)) { printk("temp config invalid, temp_value = %d\n", temp_value); sh->htc_config.exist = 0; return sh->htc_config.exist; } sh->htc_config.hw_temp_items[i].temp = temp_value; printk("hw_temp_items[%d] temp=%d\n", i, sh->htc_config.hw_temp_items[i].temp); // REG COUNT p_tmp = m_strtok(&str_table,",", 0); //printk("2. p_tmp = %s\n", p_tmp); if (p_tmp != NULL) { reg_count = simple_strtoul(p_tmp, &endp, 0); printk("reg_count = %d\n", reg_count); if (reg_count > MAX_REG_COUNT_OF_TABLE_COMPENSATION) { printk("temp config reg_count invalid\n"); sh->htc_config.exist = 0; return sh->htc_config.exist; } sh->htc_config.hw_temp_items[i].reg_count = reg_count; for ( j = 0; j < sh->htc_config.hw_temp_items[i].reg_count; j++) { p_tmp = m_strtok(&str_table,",", 0); //printk("3. p_tmp = %s\n", p_tmp); if (p_tmp != NULL) { ret = sscanf(p_tmp, "0x%08x:0x%08x", &address, &value); if (ret != 2) { printk("temp config reg/value invalid, %s\n", p_tmp); sh->htc_config.exist = 0; return sh->htc_config.exist; } sh->htc_config.hw_tem_reg_items[i][j].reg = address; sh->htc_config.hw_tem_reg_items[i][j].vaule= value; printk("i[%d],temp[%d], j[%d] reg[0x%08x:0x%08x]\n", i, sh->htc_config.hw_temp_items[i].temp, j, sh->htc_config.hw_tem_reg_items[i][j].reg, sh->htc_config.hw_tem_reg_items[i][j].vaule); } } } else { printk("temp config invalid 3 \n"); sh->htc_config.exist = 0; return sh->htc_config.exist; } } else { printk("temp config invalid 4\n"); sh->htc_config.exist = 0; return sh->htc_config.exist; } } printk("read normal temp config\n"); // NORMAL TEMP VAULE p_tmp = m_strtok(&str_table,",", 0); //printk("5. p_tmp = %s\n", p_tmp); if(p_tmp!=NULL) { temp_value = simple_strtoul(p_tmp, &endp, 0); //printk("temp_value = %d\n", temp_value); if (temp_value != 0xff) { printk("temp config invalid 6 \n"); return sh->htc_config.exist; } p_tmp = m_strtok(&str_table,",", 0); //printk("6. p_tmp = %s\n", p_tmp); if (p_tmp!=NULL) { reg_count = simple_strtoul(p_tmp, &endp, 0); printk("reg_count = %d\n", reg_count); if (reg_count > MAX_REG_COUNT_OF_TABLE_COMPENSATION) { printk("temp config invalid 7 \n"); sh->htc_config.exist = 0; return sh->htc_config.exist; } else if (reg_count > 0) { sh->htc_config.hw_temp_items[0].reg_count = reg_count; for ( j = 0; j < sh->htc_config.hw_temp_items[0].reg_count; j++) { p_tmp = m_strtok(&str_table,",", 0); //printk("7. p_tmp = %s\n", p_tmp); if (p_tmp != NULL) { ret = sscanf(p_tmp, "0x%08x:0x%08x", &address, &value); if (ret != 2) { printk("temp config invalid 8\n"); sh->htc_config.exist = 0; return sh->htc_config.exist; } sh->htc_config.hw_tem_reg_items[0][j].reg = address; sh->htc_config.hw_tem_reg_items[0][j].vaule= value; printk("temp[%d], reg[0x%08x:0x%08x]\n", sh->htc_config.hw_temp_items[0].temp, sh->htc_config.hw_tem_reg_items[0][j].reg, sh->htc_config.hw_tem_reg_items[0][j].vaule); } } } } } } } return sh->htc_config.exist; } #endif static int ssv6xxx_read_configuration(struct ssv_hw *sh) { // cp settings from configure to sh memcpy(&sh->cfg, &ssv_cfg, sizeof(struct ssv6xxx_cfg)); /** * Read configuration from external module, * such as flash/eeprom...,etc. * rf bin configuration */ // update by efuse efuse_read_all_map(sh); // update by rf bin SSV_FLASH_READ_ALL_MAP(sh); #ifdef CONFIG_ENABLE_HOST_THERMAL if(ssv6xxx_read_host_thermal_compensation_table(sh)){ sh->sc->thermal_monitor_enable = true; } #endif if (!(is_valid_ether_addr(&sh->cfg.maddr[0][0]))){ printk("invalid mac addr 1 !!\n"); WARN_ON(1); return 1; } if (SSV_IF_CHK_MAC2(sh)) { ssv6xxx_check_mac2(sh); memcpy(&sh->cfg.maddr[1][0], &ssv_cfg.maddr[1][0], ETH_ALEN); // overwrite mac2 after check mac2 } //Xtal setting 0-26M 1-40M 2-24M #if 0 if(sh->cfg.crystal_type == 26) sh->cfg.crystal_type = SSV6XXX_IQK_CFG_XTAL_26M; else if(sh->cfg.crystal_type == 40) sh->cfg.crystal_type = SSV6XXX_IQK_CFG_XTAL_40M; else if(sh->cfg.crystal_type == 24) sh->cfg.crystal_type = SSV6XXX_IQK_CFG_XTAL_24M; else if(sh->cfg.crystal_type == 25) sh->cfg.crystal_type = SSV6XXX_IQK_CFG_XTAL_25M; else { printk("Please redefine xtal_clock(wifi.cfg)!!\n"); WARN_ON(1); return 1; } #endif switch (sh->cfg.crystal_type){ case 16: sh->cfg.crystal_type = SSV6XXX_IQK_CFG_XTAL_16M; break; case 24: sh->cfg.crystal_type = SSV6XXX_IQK_CFG_XTAL_24M; break; case 26: sh->cfg.crystal_type = SSV6XXX_IQK_CFG_XTAL_26M; break; case 40: sh->cfg.crystal_type = SSV6XXX_IQK_CFG_XTAL_40M; break; case 12: sh->cfg.crystal_type = SSV6XXX_IQK_CFG_XTAL_12M; break; case 20: sh->cfg.crystal_type = SSV6XXX_IQK_CFG_XTAL_20M; break; case 25: sh->cfg.crystal_type = SSV6XXX_IQK_CFG_XTAL_25M; break; case 32: sh->cfg.crystal_type = SSV6XXX_IQK_CFG_XTAL_32M; break; default: printk("Please redefine xtal_clock(wifi.cfg)!!\n"); WARN_ON(1); return 1; break; } // flash bin configuration higher priority if (!sh->flash_config.exist) { //volt regulator(DCDC-0 LDO-1) if(sh->cfg.volt_regulator < 2) sh->cfg.volt_regulator = ssv_cfg.volt_regulator; else { printk("Please redefine volt_regulator(wifi.cfg)!!\n"); WARN_ON(1); return 1; } } SSV_ADJ_CONFIG(sh); // adjust configuration to fit each model. HAL_UPDATE_RF_TABLE(sh); // transfer efuse/flash.bin to rf table return 0; } static int ssv6xxx_hci_rx_mode(void *args) { struct ssv_softc *sc = (struct ssv_softc *)args; struct ssv_hw *sh = sc->sh; return sh->rx_mode; } static int ssv6xxx_read_hw_info(struct ssv_softc *sc) { struct ssv_hw *sh = sc->sh; //CHIP TAG SSV_GET_IC_TIME_TAG(sh); printk(KERN_INFO "CHIP TAG: %llx \n", sh->chip_tag); //Read configuraion from extern configuraion. if (ssv6xxx_read_configuration(sh)) return -ENOMEM; /* HCI register parameters */ sh->hci.hci_post_tx_cb= ssv6xxx_post_tx_cb; sh->hci.hci_tx_buf_free_cb = ssv6xxx_txbuf_free_skb; sh->hci.hci_rx_mode_cb = ssv6xxx_hci_rx_mode; sh->hci.skb_alloc = ssv_skb_alloc; sh->hci.skb_free = ssv_skb_free; sh->hci.dbgprint = ssv6xxx_hci_dbgprint; sh->hci.hci_update_flowctl_cb = ssv6xxx_hci_update_flowctl_cb; return 0; } static int ssv6xxx_init_device(struct ssv_softc *sc, const char *name) { #ifndef CONFIG_SSV6XXX_HW_DEBUG struct ieee80211_hw *hw = sc->hw; #endif struct ssv_hw *sh = NULL; bool hci_tx_aggr = false; int error = 0; struct ssv6xxx_hci_ctrl *hci_ctrl; BUG_ON(!sc->dev->platform_data); /* Initialize ssv6xxx HAL layer function*/ if ((error = ssv6xxx_init_hal(sc)) != 0) { goto err; } sh = sc->sh; /* Use original configuration to decide hci_tx_aggr function */ if (ssv_cfg.hw_caps & SSV6200_HW_CAP_HCI_TX_AGGR) hci_tx_aggr = true; /* HCI driver initialization */ if ((error = ssv6xxx_hci_register(&sh->hci, hci_tx_aggr)) != 0) goto err_sh; /* copy configuation to sh->cfg */ if ((error = ssv6xxx_read_hw_info(sc)) != 0) { goto err_hci; } if (sh->cfg.hw_caps == 0) { error = -1; goto err_hci; } /* If hci layer cannot alloc skb for hci-tx-aggr, it must turn off HCI_TX_AGGR capability */ hci_ctrl = sh->hci.hci_ctrl; if (NULL == hci_ctrl->p_tx_aggr_skb) { printk("Cannot alloc tx aggr skb, force turn off hci tx aggr\n"); sh->cfg.hw_caps = sh->cfg.hw_caps & (~(SSV6200_HW_CAP_HCI_TX_AGGR)); } if (sh->cfg.hw_caps & SSV6200_HW_CAP_HCI_TX_AGGR) { HCI_SET_CAP(sc->sh, HCI_CAP_TX_AGGR, true); } // set hci tx task trigger condition HCI_SET_TRIGGER_CONF(sc->sh, sh->cfg.hci_trigger_en, sh->cfg.hci_trigger_qlen, sh->cfg.hci_trigger_pkt_size, sh->cfg.hci_task_timeout); /* Initialize software control structure */ if ((error = ssv6xxx_init_softc(sc)) != 0) { goto err_softc; } hci_ctrl=sh->hci.hci_ctrl; HCI_RX_TASK(hci_ctrl, hci_ctrl->shi->hci_rx_cb, hci_ctrl->shi->hci_is_rx_q_full, (void *)(SSV_SC(hci_ctrl)), &hci_ctrl->rx_pkt, &hci_ctrl->rx_isr_cnt, sh->cfg.rx_max_recv_cnt); HCI_IRQ_SET_MASK(hci_ctrl, ~(hci_ctrl->int_mask)); HCI_IRQ_TRIGGER(hci_ctrl); HCI_IRQ_ENABLE(hci_ctrl); /* Set ssv6200 hardware capabilities to mac80211 stack */ ssv6xxx_set_80211_hw_capab(sc); /* Initialize ssv6200 hardware */ if ((error = ssv6xxx_init_hw(sc->sh)) != 0) { goto err_hw; } #ifndef CONFIG_SSV6XXX_HW_DEBUG /* Register to mac80211 stack */ if ((error = ieee80211_register_hw(hw)) != 0) { printk(KERN_ERR "Failed to register w. %d.\n", error); goto err_hw; } #endif #ifndef CONFIG_SSV6XXX_HW_DEBUG ssv_init_cli(dev_name(&hw->wiphy->dev), &sc->cmd_data); #else ssv_init_cli(dev_name(sc->dev), &sc->cmd_data); #endif #ifdef CONFIG_SSV6XXX_DEBUGFS ssv6xxx_init_debugfs(sc, name); #endif // CONFIG_SSV6200_DEBUGFS #ifdef CONFIG_SSV_CTL { extern int ssv_ctl_init(void); (void)ssv_ctl_init(); } #endif /* Device is ready */ sc->sc_flags |= SC_OP_DEV_READY; return 0; err_hw: ssv6xxx_deinit_hw(sc); err_softc: ssv6xxx_deinit_softc(sc); err_hci: ssv6xxx_hci_deregister(&sh->hci); err_sh: ssv6xxx_deinit_hwsh(sc); err: return error; } static void ssv6xxx_deinit_device(struct ssv_softc *sc) { printk("%s(): \n", __FUNCTION__); /* Device is not ready */ sc->sc_flags &= ~SC_OP_DEV_READY; #ifdef CONFIG_SSV_CTL { extern void ssv_ctl_exit(void); ssv_ctl_exit(); } #endif #ifdef CONFIG_SSV6XXX_DEBUGFS ssv6xxx_deinit_debugfs(sc); #endif // CONFIG_SSV6200_DEBUGFS #ifndef CONFIG_SSV6XXX_HW_DEBUG ssv_deinit_cli(dev_name(&sc->hw->wiphy->dev), &sc->cmd_data); #else ssv_deinit_cli(dev_name(sc->dev), &sc->cmd_data); #endif #ifndef CONFIG_SSV6XXX_HW_DEBUG ieee80211_unregister_hw(sc->hw); #endif ssv6xxx_deinit_hw(sc); ssv6xxx_deinit_softc(sc); ssv6xxx_hci_deregister(&sc->sh->hci); ssv6xxx_deinit_hwsh(sc); } extern struct ieee80211_ops ssv6200_ops; int ssv6xxx_dev_probe(struct platform_device *pdev) { struct ssv_softc *sc; struct ieee80211_hw *hw; int ret; if (!pdev->dev.platform_data) { dev_err(&pdev->dev, "no platform data specified!\n"); return -EINVAL; } printk("%s(): SSV6X5X device \"%s\" found !\n", __FUNCTION__, pdev->name); #ifdef SSV_MAC80211 hw = ieee80211_alloc_hw_nm(sizeof(struct ssv_softc), &ssv6200_ops,"icomm"); #else hw = ieee80211_alloc_hw(sizeof(struct ssv_softc), &ssv6200_ops); #endif if (hw == NULL) { dev_err(&pdev->dev, "No memory for ieee80211_hw\n"); return -ENOMEM; } SET_IEEE80211_DEV(hw, &pdev->dev); dev_set_drvdata(&pdev->dev, hw); memset((void *)hw->priv, 0, sizeof(struct ssv_softc)); sc = hw->priv; sc->hw = hw; sc->dev = &pdev->dev; sc->platform_dev = pdev; ret = ssv6xxx_init_device(sc, pdev->name); if (ret) { dev_err(&pdev->dev, "Failed to initialize device\n"); ieee80211_free_hw(hw); return ret; } wiphy_info(hw->wiphy, "%s\n", "SSV6X5X of iComm-semi"); return 0; } EXPORT_SYMBOL(ssv6xxx_dev_probe); void ssv6xxx_cancel_work_sync(struct ssv_softc *sc) { /* cancel beacon_miss_handler workqueue */ cancel_work_sync(&sc->beacon_miss_work); cancel_delayed_work_sync(&sc->bcast_tx_work); // cancel house keeping work cancel_work_sync(&sc->mib_edca_work); cancel_work_sync(&sc->tx_poll_work); cancel_work_sync(&sc->flowctl_work); cancel_work_sync(&sc->check_fw_status_work); cancel_work_sync(&sc->hw_restart_work); // cancel tx done work cancel_work_sync(&sc->tx_done_work); // cancel scan work cancel_delayed_work_sync(&sc->scan_ignore_work); if (sc->hw_scan_start) cancel_delayed_work_sync(&sc->scan_work); #ifdef CONFIG_ENABLE_HOST_THERMAL cancel_work_sync(&sc->thermal_monitor_work); #endif } static void ssv6xxx_stop_all_running_threads(struct ssv_softc *sc) { u32 dev_type = HCI_DEVICE_TYPE(sc->sh->hci.hci_ctrl); struct ssv6xxx_hci_ctrl *hci_ctrl; //disable rx interrupt if (dev_type == SSV_HWIF_INTERFACE_USB) SSV_DISABLE_USB_ACC(sc, 0x4); else { hci_ctrl = sc->sh->hci.hci_ctrl; HCI_IRQ_DISABLE(hci_ctrl); } if (sc->ssv_txtput.txtput_tsk) { printk(KERN_ERR "Stopping txtput task...\n"); kthread_stop(sc->ssv_txtput.txtput_tsk); while (sc->ssv_txtput.txtput_tsk != NULL) { msleep(1); } printk(KERN_ERR "txtput task is stopped.\n"); } ssv6xxx_cancel_work_sync(sc); if (sc->rx_task != NULL) { printk(KERN_ERR "Stopping RX task...\n"); kthread_stop(sc->rx_task); while (sc->rx_task != NULL) { msleep(1); } printk(KERN_ERR "RX task is stopped.\n"); } if(sc->sh->hci.hci_ctrl->hci_tx_task != NULL) { printk(KERN_ERR "Stopping HCI TX task...\n"); kthread_stop(sc->sh->hci.hci_ctrl->hci_tx_task); while(sc->sh->hci.hci_ctrl->hci_tx_task != NULL) { msleep(1); } printk(KERN_ERR "HCI TX task is stopped.\n"); } } int ssv6xxx_dev_remove(struct platform_device *pdev) { struct ieee80211_hw *hw=dev_get_drvdata(&pdev->dev); struct ssv_softc *sc=hw->priv; printk("ssv6xxx_dev_remove(): pdev=%p, hw=%p\n", pdev, hw); HCI_IGNORE_CMD(sc->sh, true); // Makes house keeping do nothing. sc->mac80211_dev_started = false; ssv6xxx_stop_all_running_threads(sc); ssv6xxx_deinit_device(sc); printk("ieee80211_free_hw(): \n"); ieee80211_free_hw(hw); pr_info("ssv6200: Driver unloaded\n"); return 0; } EXPORT_SYMBOL(ssv6xxx_dev_remove); static const struct platform_device_id ssv6xxx_id_table[] = { #ifdef SSV_SUPPORT_SSV6006 { .name = SSV6006A, .driver_data = 0, }, { .name = SSV6006C, .driver_data = 0, }, { .name = SSV6006D, .driver_data = 0, }, #endif // SSV_SUPPORT_SSV6006 {}, /* Terminating Entry */ }; MODULE_DEVICE_TABLE(platform, ssv6xxx_id_table); static struct platform_driver ssv6xxx_driver = { .probe = ssv6xxx_dev_probe, #if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0) .remove = __devexit_p(ssv6xxx_dev_remove), #else .remove = ssv6xxx_dev_remove, #endif .id_table = ssv6xxx_id_table, .driver = { .name = SSV_DRVER_NAME, .owner = THIS_MODULE, } }; #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,2,21) static int device_match_by_alias(struct device *dev,const void *data) #else static int device_match_by_alias(struct device *dev, void *data) #endif { struct device_driver *driver = dev->driver; struct _pattern { char driver_name[32]; char device_name[32]; } *pattern; pattern = (struct _pattern *)data; if (!strcmp(driver->name, pattern->driver_name) && !strcmp(dev_name(dev), pattern->device_name)) return 1; if (!strcmp(driver->name, pattern->driver_name) && !strcmp("", pattern->device_name)) return 1; else { printk("%s: driver[%s][%s], device[%s][%s]\n", __FUNCTION__, driver->name, pattern->driver_name, dev_name(dev), pattern->device_name); return 0; } } struct ssv_softc *ssv6xxx_driver_attach(char *driver_name) { struct device *dev; struct ieee80211_hw *hw; struct ssv_softc *sc; struct _pattern { char driver_name[32]; char device_name[32]; } pattern; memset(&pattern, 0, sizeof(struct _pattern)); sprintf(pattern.driver_name, "%s", driver_name); dev = driver_find_device(&ssv6xxx_driver.driver, NULL, (void *)&pattern, device_match_by_alias); if (!dev) { printk("Cannot find the driver[%s]\n", driver_name); return NULL; } hw = dev_get_drvdata(dev); sc = hw->priv; return sc; } int ssv6xxx_init(void) { return platform_driver_register(&ssv6xxx_driver); } void ssv6xxx_exit(void) { platform_driver_unregister(&ssv6xxx_driver); } EXPORT_SYMBOL(ssv6xxx_init); EXPORT_SYMBOL(ssv6xxx_exit);