wl12xx: Add support for HW channel switch
authorShahar Levi <shahar_levi@ti.com>
Thu, 8 Sep 2011 10:01:33 +0000 (13:01 +0300)
committerLuciano Coelho <coelho@ti.com>
Fri, 7 Oct 2011 05:32:27 +0000 (08:32 +0300)
The wl12xx FW supports HW channel switch.  If we don't use it,
sometimes the firmware gets confused when recalibrating to the new
channel, causing RX problems.  This commit adds HW channel switch
support by implementing the channell_switch op.

Signed-off-by: Shahar Levi <shahar_levi@ti.com>
[added one comment, remove the tx_flush and rephrased the commit message]
Signed-off-by: Luciano Coelho <coelho@ti.com>
drivers/net/wireless/wl12xx/boot.c
drivers/net/wireless/wl12xx/cmd.c
drivers/net/wireless/wl12xx/cmd.h
drivers/net/wireless/wl12xx/event.c
drivers/net/wireless/wl12xx/main.c
drivers/net/wireless/wl12xx/wl12xx.h

index 6d5664bfc37dcffa9deaf904a092b8d59e50cbb0..9b400270397b95e99ff83a40d959717cfa9eefa2 100644 (file)
@@ -503,7 +503,8 @@ static int wl1271_boot_run_firmware(struct wl1271 *wl)
                BA_SESSION_RX_CONSTRAINT_EVENT_ID |
                REMAIN_ON_CHANNEL_COMPLETE_EVENT_ID |
                INACTIVE_STA_EVENT_ID |
-               MAX_TX_RETRY_EVENT_ID;
+               MAX_TX_RETRY_EVENT_ID |
+               CHANNEL_SWITCH_COMPLETE_EVENT_ID;
 
        ret = wl1271_event_unmask(wl);
        if (ret < 0) {
index 287fe95ecb40092e826b821056ee05e093d3f3a0..8c963a6bb0a5104a970fa8bb1120dd7f26f4feca 100644 (file)
@@ -1700,3 +1700,61 @@ int wl12xx_croc(struct wl1271 *wl, u8 role_id)
 out:
        return ret;
 }
+
+int wl12xx_cmd_channel_switch(struct wl1271 *wl,
+                             struct ieee80211_channel_switch *ch_switch)
+{
+       struct wl12xx_cmd_channel_switch *cmd;
+       int ret;
+
+       wl1271_debug(DEBUG_ACX, "cmd channel switch");
+
+       cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
+       if (!cmd) {
+               ret = -ENOMEM;
+               goto out;
+       }
+
+       cmd->channel = ch_switch->channel->hw_value;
+       cmd->switch_time = ch_switch->count;
+       cmd->tx_suspend = ch_switch->block_tx;
+       cmd->flush = 0; /* this value is ignored by the FW */
+
+       ret = wl1271_cmd_send(wl, CMD_CHANNEL_SWITCH, cmd, sizeof(*cmd), 0);
+       if (ret < 0) {
+               wl1271_error("failed to send channel switch command");
+               goto out_free;
+       }
+
+out_free:
+       kfree(cmd);
+
+out:
+       return ret;
+}
+
+int wl12xx_cmd_stop_channel_switch(struct wl1271 *wl)
+{
+       struct wl12xx_cmd_stop_channel_switch *cmd;
+       int ret;
+
+       wl1271_debug(DEBUG_ACX, "cmd stop channel switch");
+
+       cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
+       if (!cmd) {
+               ret = -ENOMEM;
+               goto out;
+       }
+
+       ret = wl1271_cmd_send(wl, CMD_STOP_CHANNEL_SWICTH, cmd, sizeof(*cmd), 0);
+       if (ret < 0) {
+               wl1271_error("failed to stop channel switch command");
+               goto out_free;
+       }
+
+out_free:
+       kfree(cmd);
+
+out:
+       return ret;
+}
index 8e4d11ec0c55fd7cda5b90a1751a48031d7abb41..b7bd42769aa72eedf0cffe59716728554e063e75 100644 (file)
@@ -79,6 +79,9 @@ int wl12xx_cmd_remove_peer(struct wl1271 *wl, u8 hlid);
 int wl12xx_cmd_config_fwlog(struct wl1271 *wl);
 int wl12xx_cmd_start_fwlog(struct wl1271 *wl);
 int wl12xx_cmd_stop_fwlog(struct wl1271 *wl);
+int wl12xx_cmd_channel_switch(struct wl1271 *wl,
+                             struct ieee80211_channel_switch *ch_switch);
+int wl12xx_cmd_stop_channel_switch(struct wl1271 *wl);
 
 enum wl1271_commands {
        CMD_INTERROGATE     = 1,    /*use this to read information elements*/
@@ -677,4 +680,21 @@ struct wl12xx_cmd_stop_fwlog {
        struct wl1271_cmd_header header;
 } __packed;
 
+struct wl12xx_cmd_channel_switch {
+       struct wl1271_cmd_header header;
+
+       /* The new serving channel */
+       u8 channel;
+       /* Relative time of the serving channel switch in TBTT units */
+       u8 switch_time;
+       /* 1: Suspend TX till switch time; 0: Do not suspend TX */
+       u8 tx_suspend;
+       /* 1: Flush TX at switch time; 0: Do not flush */
+       u8 flush;
+} __packed;
+
+struct wl12xx_cmd_stop_channel_switch {
+       struct wl1271_cmd_header header;
+} __packed;
+
 #endif /* __WL1271_CMD_H__ */
index e66db69f8d17e38dd8abd76668fa4aaa06109225..674ad2a9e40972be89d4188150917782f05da90e 100644 (file)
@@ -300,6 +300,21 @@ static int wl1271_event_process(struct wl1271 *wl, struct event_mailbox *mbox)
                        wl1271_stop_ba_event(wl);
        }
 
+       if ((vector & CHANNEL_SWITCH_COMPLETE_EVENT_ID) && !is_ap) {
+               wl1271_debug(DEBUG_EVENT, "CHANNEL_SWITCH_COMPLETE_EVENT_ID. "
+                                         "status = 0x%x",
+                                         mbox->channel_switch_status);
+               /*
+                * That event uses for two cases:
+                * 1) channel switch complete with status=0
+                * 2) channel switch failed status=1
+                */
+               if (test_and_clear_bit(WL1271_FLAG_CS_PROGRESS, &wl->flags) &&
+                   (wl->vif))
+                       ieee80211_chswitch_done(wl->vif,
+                               mbox->channel_switch_status ? false : true);
+       }
+
        if ((vector & DUMMY_PACKET_EVENT_ID)) {
                wl1271_debug(DEBUG_EVENT, "DUMMY_PACKET_ID_EVENT_ID");
                if (wl->vif)
index b1b405b576cc9df477d1d7b099111e51475ccf35..6b8a8a339f96ecf091151dd21e0c5d17136dc4a3 100644 (file)
@@ -2222,6 +2222,11 @@ static int wl1271_unjoin(struct wl1271 *wl)
 {
        int ret;
 
+       if (test_and_clear_bit(WL1271_FLAG_CS_PROGRESS, &wl->flags)) {
+               wl12xx_cmd_stop_channel_switch(wl);
+               ieee80211_chswitch_done(wl->vif, false);
+       }
+
        /* to stop listening to a channel, we disconnect */
        ret = wl12xx_cmd_role_stop_sta(wl);
        if (ret < 0)
@@ -4130,6 +4135,37 @@ static int wl12xx_set_bitrate_mask(struct ieee80211_hw *hw,
        return 0;
 }
 
+static void wl12xx_op_channel_switch(struct ieee80211_hw *hw,
+                                    struct ieee80211_channel_switch *ch_switch)
+{
+       struct wl1271 *wl = hw->priv;
+       int ret;
+
+       wl1271_debug(DEBUG_MAC80211, "mac80211 channel switch");
+
+       mutex_lock(&wl->mutex);
+
+       if (unlikely(wl->state == WL1271_STATE_OFF)) {
+               mutex_unlock(&wl->mutex);
+               ieee80211_chswitch_done(wl->vif, false);
+               return;
+       }
+
+       ret = wl1271_ps_elp_wakeup(wl);
+       if (ret < 0)
+               goto out;
+
+       ret = wl12xx_cmd_channel_switch(wl, ch_switch);
+
+       if (!ret)
+               set_bit(WL1271_FLAG_CS_PROGRESS, &wl->flags);
+
+       wl1271_ps_elp_sleep(wl);
+
+out:
+       mutex_unlock(&wl->mutex);
+}
+
 static bool wl1271_tx_frames_pending(struct ieee80211_hw *hw)
 {
        struct wl1271 *wl = hw->priv;
@@ -4406,6 +4442,7 @@ static const struct ieee80211_ops wl1271_ops = {
        .ampdu_action = wl1271_op_ampdu_action,
        .tx_frames_pending = wl1271_tx_frames_pending,
        .set_bitrate_mask = wl12xx_set_bitrate_mask,
+       .channel_switch = wl12xx_op_channel_switch,
        CFG80211_TESTMODE_CMD(wl1271_tm_cmd)
 };
 
index 997f532450111577905c055eaacf122b6035b3fb..02644b4fb6978b7109b75958d546b629a134c6ee 100644 (file)
@@ -348,6 +348,7 @@ enum wl12xx_flags {
        WL1271_FLAG_SOFT_GEMINI,
        WL1271_FLAG_RX_STREAMING_STARTED,
        WL1271_FLAG_RECOVERY_IN_PROGRESS,
+       WL1271_FLAG_CS_PROGRESS,
 };
 
 struct wl1271_link {