ath6kl: cold reset target after host warm boot
[cascardo/linux.git] / drivers / net / wireless / ath / ath6kl / init.c
index f21fa32..4ad45bb 100644 (file)
@@ -1549,10 +1549,89 @@ static const char *ath6kl_init_get_hif_name(enum ath6kl_hif_type type)
        return NULL;
 }
 
+
+static const struct fw_capa_str_map {
+       int id;
+       const char *name;
+} fw_capa_map[] = {
+       { ATH6KL_FW_CAPABILITY_HOST_P2P, "host-p2p" },
+       { ATH6KL_FW_CAPABILITY_SCHED_SCAN, "sched-scan" },
+       { ATH6KL_FW_CAPABILITY_STA_P2PDEV_DUPLEX, "sta-p2pdev-duplex" },
+       { ATH6KL_FW_CAPABILITY_INACTIVITY_TIMEOUT, "inactivity-timeout" },
+       { ATH6KL_FW_CAPABILITY_RSN_CAP_OVERRIDE, "rsn-cap-override" },
+       { ATH6KL_FW_CAPABILITY_WOW_MULTICAST_FILTER, "wow-mc-filter" },
+       { ATH6KL_FW_CAPABILITY_BMISS_ENHANCE, "bmiss-enhance" },
+       { ATH6KL_FW_CAPABILITY_SCHED_SCAN_MATCH_LIST, "sscan-match-list" },
+       { ATH6KL_FW_CAPABILITY_RSSI_SCAN_THOLD, "rssi-scan-thold" },
+       { ATH6KL_FW_CAPABILITY_CUSTOM_MAC_ADDR, "custom-mac-addr" },
+       { ATH6KL_FW_CAPABILITY_TX_ERR_NOTIFY, "tx-err-notify" },
+       { ATH6KL_FW_CAPABILITY_REGDOMAIN, "regdomain" },
+       { ATH6KL_FW_CAPABILITY_SCHED_SCAN_V2, "sched-scan-v2" },
+       { ATH6KL_FW_CAPABILITY_HEART_BEAT_POLL, "hb-poll" },
+};
+
+static const char *ath6kl_init_get_fw_capa_name(unsigned int id)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(fw_capa_map); i++) {
+               if (fw_capa_map[i].id == id)
+                       return fw_capa_map[i].name;
+       }
+
+       return "<unknown>";
+}
+
+static void ath6kl_init_get_fwcaps(struct ath6kl *ar, char *buf, size_t buf_len)
+{
+       u8 *data = (u8 *) ar->fw_capabilities;
+       size_t trunc_len, len = 0;
+       int i, index, bit;
+       char *trunc = "...";
+
+       for (i = 0; i < ATH6KL_FW_CAPABILITY_MAX; i++) {
+               index = i / 8;
+               bit = i % 8;
+
+               if (index >= sizeof(ar->fw_capabilities) * 4)
+                       break;
+
+               if (buf_len - len < 4) {
+                       ath6kl_warn("firmware capability buffer too small!\n");
+
+                       /* add "..." to the end of string */
+                       trunc_len = strlen(trunc) + 1;
+                       strncpy(buf + buf_len - trunc_len, trunc, trunc_len);
+
+                       return;
+               }
+
+               if (data[index] & (1 << bit)) {
+                       len += scnprintf(buf + len, buf_len - len, "%s,",
+                                           ath6kl_init_get_fw_capa_name(i));
+               }
+       }
+
+       /* overwrite the last comma */
+       if (len > 0)
+               len--;
+
+       buf[len] = '\0';
+}
+
+static int ath6kl_init_hw_reset(struct ath6kl *ar)
+{
+       ath6kl_dbg(ATH6KL_DBG_BOOT, "cold resetting the device");
+
+       return ath6kl_diag_write32(ar, RESET_CONTROL_ADDRESS,
+                                  cpu_to_le32(RESET_CONTROL_COLD_RST));
+}
+
 static int __ath6kl_init_hw_start(struct ath6kl *ar)
 {
        long timeleft;
        int ret, i;
+       char buf[200];
 
        ath6kl_dbg(ATH6KL_DBG_BOOT, "hw start\n");
 
@@ -1569,24 +1648,35 @@ static int __ath6kl_init_hw_start(struct ath6kl *ar)
                goto err_power_off;
 
        /* Do we need to finish the BMI phase */
-       /* FIXME: return error from ath6kl_bmi_done() */
-       if (ath6kl_bmi_done(ar)) {
-               ret = -EIO;
+       ret = ath6kl_bmi_done(ar);
+       if (ret)
                goto err_power_off;
-       }
 
        /*
         * The reason we have to wait for the target here is that the
         * driver layer has to init BMI in order to set the host block
         * size.
         */
-       if (ath6kl_htc_wait_target(ar->htc_target)) {
-               ret = -EIO;
+       ret = ath6kl_htc_wait_target(ar->htc_target);
+
+       if (ret == -ETIMEDOUT) {
+               /*
+                * Most likely USB target is in odd state after reboot and
+                * needs a reset. A cold reset makes the whole device
+                * disappear from USB bus and initialisation starts from
+                * beginning.
+                */
+               ath6kl_warn("htc wait target timed out, resetting device\n");
+               ath6kl_init_hw_reset(ar);
+               goto err_power_off;
+       } else if (ret) {
+               ath6kl_err("htc wait target failed: %d\n", ret);
                goto err_power_off;
        }
 
-       if (ath6kl_init_service_ep(ar)) {
-               ret = -EIO;
+       ret = ath6kl_init_service_ep(ar);
+       if (ret) {
+               ath6kl_err("Endpoint service initilisation failed: %d\n", ret);
                goto err_cleanup_scatter;
        }
 
@@ -1617,6 +1707,8 @@ static int __ath6kl_init_hw_start(struct ath6kl *ar)
                            ar->wiphy->fw_version,
                            ar->fw_api,
                            test_bit(TESTMODE, &ar->flag) ? " testmode" : "");
+               ath6kl_init_get_fwcaps(ar, buf, sizeof(buf));
+               ath6kl_info("firmware supports: %s\n", buf);
        }
 
        if (ar->version.abi_ver != ATH6KL_ABI_VERSION) {
@@ -1715,38 +1807,6 @@ void ath6kl_init_hw_restart(struct ath6kl *ar)
        }
 }
 
-/* FIXME: move this to cfg80211.c and rename to ath6kl_cfg80211_vif_stop() */
-void ath6kl_cleanup_vif(struct ath6kl_vif *vif, bool wmi_ready)
-{
-       static u8 bcast_mac[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
-       bool discon_issued;
-
-       netif_stop_queue(vif->ndev);
-
-       clear_bit(WLAN_ENABLED, &vif->flags);
-
-       if (wmi_ready) {
-               discon_issued = test_bit(CONNECTED, &vif->flags) ||
-                               test_bit(CONNECT_PEND, &vif->flags);
-               ath6kl_disconnect(vif);
-               del_timer(&vif->disconnect_timer);
-
-               if (discon_issued)
-                       ath6kl_disconnect_event(vif, DISCONNECT_CMD,
-                                               (vif->nw_type & AP_NETWORK) ?
-                                               bcast_mac : vif->bssid,
-                                               0, NULL, 0);
-       }
-
-       if (vif->scan_req) {
-               cfg80211_scan_done(vif->scan_req, true);
-               vif->scan_req = NULL;
-       }
-
-       /* need to clean up enhanced bmiss detection fw state */
-       ath6kl_cfg80211_sta_bmiss_enhance(vif, false);
-}
-
 void ath6kl_stop_txrx(struct ath6kl *ar)
 {
        struct ath6kl_vif *vif, *tmp_vif;
@@ -1766,7 +1826,7 @@ void ath6kl_stop_txrx(struct ath6kl *ar)
        list_for_each_entry_safe(vif, tmp_vif, &ar->vif_list, list) {
                list_del(&vif->list);
                spin_unlock_bh(&ar->list_lock);
-               ath6kl_cleanup_vif(vif, test_bit(WMI_READY, &ar->flag));
+               ath6kl_cfg80211_vif_stop(vif, test_bit(WMI_READY, &ar->flag));
                rtnl_lock();
                ath6kl_cfg80211_vif_cleanup(vif);
                rtnl_unlock();
@@ -1797,11 +1857,7 @@ void ath6kl_stop_txrx(struct ath6kl *ar)
         * Try to reset the device if we can. The driver may have been
         * configure NOT to reset the target during a debug session.
         */
-       ath6kl_dbg(ATH6KL_DBG_TRC,
-                  "attempting to reset target on instance destroy\n");
-       ath6kl_reset_device(ar, ar->target_type, true, true);
-
-       clear_bit(WLAN_ENABLED, &ar->flag);
+       ath6kl_init_hw_reset(ar);
 
        up(&ar->sem);
 }