[RAMEN9610-10619][Robusta2][9610] wlbt: [ACS] Driver changes
authorHimani Gupta <himani.g2@samsung.com>
Mon, 24 Dec 2018 13:31:29 +0000 (19:01 +0530)
committerhskang <hs1218.kang@samsung.com>
Sat, 26 Jan 2019 06:33:17 +0000 (15:33 +0900)
Added support for ACS in driver.

Change-Id: Iad87233b170ea488656d92665bba1172353da0ec
SCSC-Bug-Id: SSB-47573
Signed-off-by: Himani Gupta <himani.g2@samsung.com>
drivers/net/wireless/scsc/Kconfig
drivers/net/wireless/scsc/cfg80211_ops.c
drivers/net/wireless/scsc/dev.c
drivers/net/wireless/scsc/dev.h
drivers/net/wireless/scsc/mgt.c
drivers/net/wireless/scsc/mgt.h
drivers/net/wireless/scsc/netif.c
drivers/net/wireless/scsc/nl80211_vendor.c
drivers/net/wireless/scsc/nl80211_vendor.h
drivers/net/wireless/scsc/rx.c

index 588d5c5d69a3f6b49789db05b18a92bab0bd5779..f534552b561df56da16acdf5adfb24561049ac73 100644 (file)
@@ -167,3 +167,10 @@ config SCSC_WLAN_SINGLE_ANTENNA
        ---help---
          This option tells if there is support for single
          antenna or dual antenna.
+config SCSC_WLAN_ACS_ENABLE
+        bool "ACS Support"
+        default y
+        ---help---
+          This option tells if automatic channel selection is
+         supported or not.
+
index 066500dcb7b760a68a494b99e05f7640c3fa8025..d49849dbefa815cc45bf6bc3dfeb77d4af96ad81 100755 (executable)
@@ -2273,6 +2273,9 @@ int slsi_start_ap(struct wiphy *wiphy, struct net_device *dev,
        if ((indoor_channel == 1)
 #ifdef CONFIG_SCSC_WLAN_WIFI_SHARING
            || (wifi_sharing_channel_switched == 1)
+#endif
+#ifdef CONFIG_SCSC_WLAN_ACS_ENABLE
+           || (sdev->acs_channel_switched == true)
 #endif
        )
                cfg80211_ch_switch_notify(dev, &settings->chandef);
index 2ef0d2d4f4f6670314200f50bf027b3ea3595058..85ed92624b7b4fda4606a70582eeb3997e1d5c3a 100644 (file)
@@ -267,6 +267,7 @@ struct slsi_dev *slsi_dev_attach(struct device *dev, struct scsc_mx *core, struc
        sdev->local_mib.mib_file_name = local_mib_file;
        sdev->maddr_file_name = maddr_file;
        sdev->device_config.qos_info = -1;
+       sdev->acs_channel_switched = false;
        memset(&sdev->chip_info_mib, 0xFF, sizeof(struct slsi_chip_info_mib));
 
 #ifdef CONFIG_SCSC_WLAN_WIFI_SHARING
index 402833a102dc22d5456f58922af0c66f0bdfacd8..d1f038f5e603dd8c857f9924d8f6fa41dcad1f44 100755 (executable)
@@ -315,6 +315,7 @@ struct slsi_scan_result {
 struct slsi_scan {
        /* When a Scan is running this not NULL. */
        struct cfg80211_scan_request       *scan_req;
+       struct slsi_acs_request            *acs_request;
        struct cfg80211_sched_scan_request *sched_req;
        bool                               requeue_timeout_work;
 
@@ -1103,6 +1104,7 @@ struct slsi_dev {
        struct slsi_traffic_mon_clients    traffic_mon_clients;
        /*Store vif index corresponding to rtt id for FTM*/
        u16                             rtt_vif[8];
+       bool                            acs_channel_switched;
 };
 
 /* Compact representation of channels a ESS has been seen on
index e213548b777f31c6894cc216b7e22260943872c4..da293614a53b41cf55b8082927d6083416b37365 100755 (executable)
@@ -5020,3 +5020,22 @@ void slsi_update_supported_channels_regd_flags(struct slsi_dev *sdev)
                }
        }
 }
+
+int slsi_find_chan_idx(u16 chan, u8 hw_mode)
+{
+       int idx = 0, i = 0;
+       u16 slsi_5ghz_channels_list[25] = {36, 40, 44, 48, 52, 56, 60, 64, 100, 104, 108, 112, 116, 120, 124, 128, 132,
+                                     136, 140, 144, 149, 153, 157, 161, 165};
+
+       if (hw_mode == SLSI_ACS_MODE_IEEE80211B || hw_mode == SLSI_ACS_MODE_IEEE80211G) {
+               idx = chan - 1;
+               return idx;
+       }
+       for (i = 0; i < 25; i++) {
+               if (chan == slsi_5ghz_channels_list[i]) {
+                       idx = i;
+                       break;
+               }
+       }
+       return idx;
+}
index dd8498e27b5410e93ba45cec2b337bd2a39952ea..d16e11952a687cc4fb8c875e704ea4771d1c491c 100755 (executable)
@@ -476,4 +476,7 @@ int slsi_test_send_hanged_vendor_event(struct net_device *dev);
 #endif
 void slsi_wlan_dump_public_action_subtype(struct ieee80211_mgmt *mgmt, bool tx);
 void slsi_reset_channel_flags(struct slsi_dev *sdev);
+
+int slsi_find_chan_idx(u16 chan, u8 hw_mode);
+
 #endif /*__SLSI_MGT_H__*/
index ce22fb35b19da20ae5f103f1ddbfd5f620982a9c..82094c0fd42dd6bdfe3c5e76e7d9c9fd80d2be60 100755 (executable)
@@ -258,6 +258,7 @@ static int slsi_net_stop(struct net_device *dev)
 
        sdev->allow_switch_40_mhz = true;
        sdev->allow_switch_80_mhz = true;
+       sdev->acs_channel_switched = false;
        slsi_wakeunlock(&sdev->wlan_wl);
        return 0;
 }
index fc7be8822547fb193c7de15572bd1553dadac5de..d011b1db74ff82ce672f7f9b678eab8ddbf7a685 100755 (executable)
@@ -85,6 +85,8 @@ char *slsi_print_event_name(int event_id)
                return "SLSI_NL80211_RTT_RESULT_EVENT";
        case SLSI_NL80211_RTT_COMPLETE_EVENT:
                return "SLSI_NL80211_RTT_COMPLETE_EVENT";
+       case SLSI_NL80211_VENDOR_ACS_EVENT:
+               return "SLSI_NL80211_VENDOR_ACS_EVENT";
        default:
                return "UNKNOWN_EVENT";
        }
@@ -6160,6 +6162,147 @@ exit:
 
 #endif /* CONFIG_SCSC_WLAN_ENHANCED_LOGGING */
 
+static int slsi_acs_validate_width_hw_mode(struct slsi_acs_request *request)
+{
+       if (request->hw_mode != SLSI_ACS_MODE_IEEE80211A && request->hw_mode != SLSI_ACS_MODE_IEEE80211B &&
+           request->hw_mode != SLSI_ACS_MODE_IEEE80211G)
+               return -EINVAL;
+       if (request->ch_width != 20 && request->ch_width != 40 && request->ch_width != 80)
+               return -EINVAL;
+       return 0;
+}
+
+static int slsi_acs_init(struct wiphy *wiphy,
+                        struct wireless_dev *wdev, const void *data, int len)
+{
+       struct slsi_dev    *sdev = SDEV_FROM_WIPHY(wiphy);
+       struct net_device *dev = wdev->netdev;
+       struct netdev_vif  *ndev_vif;
+       struct slsi_acs_request *request;
+       int                      temp;
+       int                      type;
+       const struct nlattr      *attr;
+       int r = 0;
+       u32 *freq_list;
+       int freq_list_len = 0;
+
+       SLSI_INFO(sdev, "SUBCMD_ACS_INIT Received\n");
+       if (slsi_is_test_mode_enabled()) {
+               SLSI_ERR(sdev, "Not supported in WlanLite mode\n");
+               return -EOPNOTSUPP;
+       }
+       if (wdev->iftype != NL80211_IFTYPE_AP) {
+               SLSI_ERR(sdev, "Invalid iftype: %d\n", wdev->iftype);
+               return -EINVAL;
+       }
+       if (!dev) {
+               SLSI_ERR(sdev, "Dev not found!\n");
+               return -ENODEV;
+       }
+       request = kcalloc(1, sizeof(*request), GFP_KERNEL);
+       if (!request) {
+               SLSI_ERR(sdev, "No memory for request!");
+               return -ENOMEM;
+       }
+       ndev_vif = netdev_priv(dev);
+
+       SLSI_MUTEX_LOCK(ndev_vif->scan_mutex);
+       nla_for_each_attr(attr, data, len, temp) {
+               type = nla_type(attr);
+               switch (type) {
+               case SLSI_ACS_ATTR_HW_MODE:
+               {
+                       request->hw_mode = nla_get_u8(attr);
+                       SLSI_INFO(sdev, "ACS hw mode: %d\n", request->hw_mode);
+                       break;
+               }
+               case SLSI_ACS_ATTR_CHWIDTH:
+               {
+                       request->ch_width = nla_get_u16(attr);
+                       SLSI_INFO(sdev, "ACS ch_width: %d\n", request->ch_width);
+                       break;
+               }
+               case SLSI_ACS_ATTR_FREQ_LIST:
+               {
+                       freq_list =  kmalloc(nla_len(attr), GFP_KERNEL);
+                       if (!freq_list) {
+                               SLSI_ERR(sdev, "No memory for frequency list!");
+                               kfree(request);
+                               SLSI_MUTEX_UNLOCK(ndev_vif->scan_mutex);
+                               return -ENOMEM;
+                       }
+                       memcpy(freq_list, nla_data(attr), nla_len(attr));
+                       freq_list_len = nla_len(attr);
+                       break;
+               }
+               default:
+                       SLSI_ERR(sdev, "Invalid type : %d\n", type);
+                       break;
+               }
+       }
+
+       r = slsi_acs_validate_width_hw_mode(request);
+       if (r == 0 && freq_list_len) {
+               struct ieee80211_channel *channels[freq_list_len];
+               struct slsi_acs_chan_info ch_info[MAX_CHAN_VALUE_ACS];
+               int i = 0, num_channels = 0;
+               int idx;
+               u32 chan_flags = (IEEE80211_CHAN_INDOOR_ONLY | IEEE80211_CHAN_RADAR |
+                                             IEEE80211_CHAN_DISABLED |
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(3, 10, 13)
+                                             IEEE80211_CHAN_PASSIVE_SCAN
+#else
+                                             IEEE80211_CHAN_NO_IR
+#endif
+                                            );
+
+               memset(channels, 0, sizeof(channels));
+               memset(&ch_info, 0, sizeof(ch_info));
+               for (i = 0; i < freq_list_len; i++) {
+                       channels[num_channels] = ieee80211_get_channel(wiphy, freq_list[i]);
+                       if (!channels[num_channels]) {
+                               SLSI_INFO(sdev, "Ignore invalid freq:%d in freq list\n", freq_list[i]);
+                       } else if (channels[num_channels]->flags & chan_flags) {
+                               SLSI_INFO(sdev, "Skip invalid channel:%d for ACS\n", channels[num_channels]->hw_value);
+                       } else {
+                               idx = slsi_find_chan_idx(channels[num_channels]->hw_value, request->hw_mode);
+                               ch_info[idx].chan = channels[num_channels]->hw_value;
+                               num_channels++;
+                       }
+               }
+               for (i = 0; i < 25; i++)
+                       SLSI_INFO(sdev, "Channel value:%d\n", ch_info[i].chan);      /*will remove after testing */
+               if (request->hw_mode == SLSI_ACS_MODE_IEEE80211A)
+                       request->ch_list_len = 25;
+               else
+                       request->ch_list_len = 14;
+               memcpy(&request->acs_chan_info[0], &ch_info[0], sizeof(ch_info));
+               ndev_vif->scan[SLSI_SCAN_HW_ID].acs_request = request;
+               ndev_vif->scan[SLSI_SCAN_HW_ID].is_blocking_scan = false;
+               r = slsi_mlme_add_scan(sdev,
+                                      dev,
+                                      FAPI_SCANTYPE_AP_AUTO_CHANNEL_SELECTION,
+                                      FAPI_REPORTMODE_REAL_TIME,
+                                      0,    /* n_ssids */
+                                      NULL, /* ssids */
+                                      num_channels,
+                                      channels,
+                                      NULL,
+                                      NULL,                   /* ie */
+                                      0,                      /* ie_len */
+                                      ndev_vif->scan[SLSI_SCAN_HW_ID].is_blocking_scan);
+       } else {
+               SLSI_ERR(sdev, "Invalid freq_list len:%d or ch_width:%d or hw_mode:%d\n", freq_list_len,
+                        request->ch_width, request->hw_mode);
+               r = -EINVAL;
+               kfree(request);
+       }
+       SLSI_INFO(sdev, "SUBCMD_ACS_INIT Received 7 return value:%d\n", r);   /*will remove after testing */
+       kfree(freq_list);
+       SLSI_MUTEX_UNLOCK(ndev_vif->scan_mutex);
+       return r;
+}
+
 static const struct  nl80211_vendor_cmd_info slsi_vendor_events[] = {
        { OUI_GOOGLE, SLSI_NL80211_SIGNIFICANT_CHANGE_EVENT },
        { OUI_GOOGLE, SLSI_NL80211_HOTLIST_AP_FOUND_EVENT },
@@ -6187,7 +6330,8 @@ static const struct  nl80211_vendor_cmd_info slsi_vendor_events[] = {
        { OUI_GOOGLE,  SLSI_NL80211_NAN_DISCOVERY_ENGINE_EVENT},
        { OUI_GOOGLE,  SLSI_NL80211_NAN_DISABLED_EVENT},
        { OUI_GOOGLE,  SLSI_NL80211_RTT_RESULT_EVENT},
-       { OUI_GOOGLE,  SLSI_NL80211_RTT_COMPLETE_EVENT}
+       { OUI_GOOGLE,  SLSI_NL80211_RTT_COMPLETE_EVENT},
+       { OUI_SAMSUNG, SLSI_NL80211_VENDOR_ACS_EVENT}
 };
 
 static const struct wiphy_vendor_command     slsi_vendor_cmd[] = {
@@ -6610,7 +6754,15 @@ static const struct wiphy_vendor_command     slsi_vendor_cmd[] = {
                },
                .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV,
                .doit = slsi_rtt_cancel_config
-       }
+       },
+       {
+               {
+                       .vendor_id = OUI_SAMSUNG,
+                       .subcmd = SLSI_NL80211_VENDOR_SUBCMD_ACS_INIT
+               },
+               .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV,
+               .doit = slsi_acs_init
+       },
 };
 
 void slsi_nl80211_vendor_deinit(struct slsi_dev *sdev)
index 68c841c11a5a76d811ca0d9ffd30aebcb7e726b4..1d75011004db8f3dce60b2dbda1e9688a1323b82 100755 (executable)
@@ -96,6 +96,33 @@ enum SLSI_ROAM_ATTRIBUTES {
        SLSI_NL_ATTR_ROAM_STATE
 };
 
+enum slsi_acs_attr_offload {
+       SLSI_ACS_ATTR_CHANNEL_INVALID = 0,
+       SLSI_ACS_ATTR_PRIMARY_CHANNEL,
+       SLSI_ACS_ATTR_SECONDARY_CHANNEL,
+       SLSI_ACS_ATTR_HW_MODE,
+       SLSI_ACS_ATTR_HT_ENABLED,
+       SLSI_ACS_ATTR_HT40_ENABLED,
+       SLSI_ACS_ATTR_VHT_ENABLED,
+       SLSI_ACS_ATTR_CHWIDTH,
+       SLSI_ACS_ATTR_CH_LIST,
+       SLSI_ACS_ATTR_VHT_SEG0_CENTER_CHANNEL,
+       SLSI_ACS_ATTR_VHT_SEG1_CENTER_CHANNEL,
+       SLSI_ACS_ATTR_FREQ_LIST,
+       /* keep last */
+       SLSI_ACS_ATTR_AFTER_LAST,
+       SLSI_ACS_ATTR_MAX =
+       SLSI_ACS_ATTR_AFTER_LAST - 1
+};
+
+enum slsi_acs_hw_mode {
+       SLSI_ACS_MODE_IEEE80211B,
+       SLSI_ACS_MODE_IEEE80211G,
+       SLSI_ACS_MODE_IEEE80211A,
+       SLSI_ACS_MODE_IEEE80211AD,
+       SLSI_ACS_MODE_IEEE80211ANY,
+};
+
 enum SLSI_NAN_REPLY_ATTRIBUTES {
        NAN_REPLY_ATTR_STATUS_TYPE,
        NAN_REPLY_ATTR_VALUE,
@@ -492,6 +519,7 @@ enum slsi_hal_vendor_subcmds {
 enum slsi_supp_vendor_subcmds {
        SLSI_NL80211_VENDOR_SUBCMD_UNSPEC = 0,
        SLSI_NL80211_VENDOR_SUBCMD_KEY_MGMT_SET_KEY,
+       SLSI_NL80211_VENDOR_SUBCMD_ACS_INIT,
 };
 
 enum slsi_vendor_event_values {
@@ -517,7 +545,8 @@ enum slsi_vendor_event_values {
        SLSI_NL80211_NAN_DISCOVERY_ENGINE_EVENT,
        SLSI_NL80211_NAN_DISABLED_EVENT,
        SLSI_NL80211_RTT_RESULT_EVENT,
-       SLSI_NL80211_RTT_COMPLETE_EVENT
+       SLSI_NL80211_RTT_COMPLETE_EVENT,
+       SLSI_NL80211_VENDOR_ACS_EVENT
 };
 
 enum slsi_lls_interface_mode {
@@ -1612,6 +1641,34 @@ struct slsi_rtt_config {
        u16 LCR_request;              /* 1: request LCR, 0: do not request LCR */
 };
 
+#define MAX_CHAN_VALUE_ACS 25  /*Max number of supported channel is 25*/
+
+struct slsi_acs_chan_info {
+       u16 chan;
+       u8 num_ap;
+       u8 num_bss_load_ap;
+       u8 total_chan_utilization;
+       u8 avg_chan_utilization;
+       u8 rssi_factor;
+       u8 adj_rssi_factor;
+};
+
+struct slsi_acs_selected_channels {
+       u8 pri_channel;
+       u8 sec_channel;
+       u8 vht_seg0_center_ch;
+       u8 vht_seg1_center_ch;
+       u16 ch_width;
+       enum slsi_acs_hw_mode hw_mode;
+};
+
+struct slsi_acs_request {
+       struct slsi_acs_chan_info acs_chan_info[MAX_CHAN_VALUE_ACS];
+       u8 hw_mode;
+       u16 ch_width;
+       u8 ch_list_len;
+};
+
 void slsi_nl80211_vendor_init(struct slsi_dev *sdev);
 void slsi_nl80211_vendor_deinit(struct slsi_dev *sdev);
 u8 slsi_gscan_get_scan_policy(enum wifi_band band);
index 16e3b24673dff1479ce6da8351eea8f7bef227d2..9d1fb2f887699da194b38876e93300808613bef3 100755 (executable)
@@ -28,7 +28,6 @@
 #if (LINUX_VERSION_CODE <  KERNEL_VERSION(3, 14, 0))
 #include "porting_imx.h"
 #endif
-
 struct ieee80211_channel *slsi_find_scan_channel(struct slsi_dev *sdev, struct ieee80211_mgmt *mgmt, size_t mgmt_len, u16 freq)
 {
        int      ielen = mgmt_len - (mgmt->u.beacon.variable - (u8 *)mgmt);
@@ -535,6 +534,384 @@ void slsi_scan_complete(struct slsi_dev *sdev, struct net_device *dev, u16 scan_
        SLSI_MUTEX_UNLOCK(ndev_vif->scan_result_mutex);
 }
 
+int slsi_send_acs_event(struct slsi_dev *sdev, struct slsi_acs_selected_channels acs_selected_channels)
+{
+       struct sk_buff                         *skb = NULL;
+       u8 err = 0;
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0))
+       skb = cfg80211_vendor_event_alloc(sdev->wiphy, NULL, NLMSG_DEFAULT_SIZE,
+                                         SLSI_NL80211_VENDOR_ACS_EVENT, GFP_KERNEL);
+#else
+       skb = cfg80211_vendor_event_alloc(sdev->wiphy, NLMSG_DEFAULT_SIZE,
+                                         SLSI_NL80211_VENDOR_ACS_EVENT, GFP_KERNEL);
+#endif
+       if (!skb) {
+               SLSI_ERR_NODEV("Failed to allocate skb for VENDOR ACS event\n");
+               return -ENOMEM;
+       }
+       err |= nla_put_u8(skb, SLSI_ACS_ATTR_PRIMARY_CHANNEL, acs_selected_channels.pri_channel);
+       err |= nla_put_u8(skb, SLSI_ACS_ATTR_SECONDARY_CHANNEL, acs_selected_channels.sec_channel);
+       err |= nla_put_u8(skb, SLSI_ACS_ATTR_VHT_SEG0_CENTER_CHANNEL, acs_selected_channels.vht_seg0_center_ch);
+       err |= nla_put_u8(skb, SLSI_ACS_ATTR_VHT_SEG1_CENTER_CHANNEL, acs_selected_channels.vht_seg1_center_ch);
+       err |= nla_put_u16(skb, SLSI_ACS_ATTR_CHWIDTH, acs_selected_channels.ch_width);
+       err |= nla_put_u8(skb, SLSI_ACS_ATTR_HW_MODE, acs_selected_channels.hw_mode);
+       SLSI_DBG3(sdev, SLSI_MLME, "pri_channel=%d,sec_channel=%d,vht_seg0_center_ch=%d,"
+                               "vht_seg1_center_ch=%d, ch_width=%d, hw_mode=%d\n",
+                               acs_selected_channels.pri_channel, acs_selected_channels.sec_channel,
+                               acs_selected_channels.vht_seg0_center_ch, acs_selected_channels.vht_seg1_center_ch,
+                               acs_selected_channels.ch_width, acs_selected_channels.hw_mode);
+       if (err) {
+               SLSI_ERR_NODEV("Failed nla_put err=%d\n", err);
+               slsi_kfree_skb(skb);
+               return -EINVAL;
+       }
+       SLSI_INFO(sdev, "Event: SLSI_NL80211_VENDOR_ACS_EVENT(%d)\n", SLSI_NL80211_VENDOR_ACS_EVENT);
+       cfg80211_vendor_event(skb, GFP_KERNEL);
+       return 0;
+}
+
+int slsi_set_2g_auto_channel(struct slsi_dev *sdev, struct netdev_vif  *ndev_vif,
+                            struct slsi_acs_selected_channels *acs_selected_channels,
+                            struct slsi_acs_chan_info *ch_info)
+{
+       int i = 0, j = 0, adjacent_rssi, avg_load, total_num_ap, total_rssi;
+       bool all_bss_load = true, none_bss_load = true;
+       int  min_avg_chan_utilization = INT_MAX, min_adjacent_rssi = INT_MAX;
+       int ch_idx_min_load = 0, ch_idx_min_rssi = 0;
+       int min_avg_chan_utilization_20 = INT_MAX, min_adjacent_rssi_20 = INT_MAX;
+       int ch_idx_min_load_20 = 0, ch_idx_min_rssi_20 = 0;
+       int ret = 0;
+       int ch_list_len = ndev_vif->scan[SLSI_SCAN_HW_ID].acs_request->ch_list_len;
+
+       acs_selected_channels->ch_width = ndev_vif->scan[SLSI_SCAN_HW_ID].acs_request->ch_width;
+       acs_selected_channels->hw_mode = ndev_vif->scan[SLSI_SCAN_HW_ID].acs_request->hw_mode;
+
+       SLSI_DBG3(sdev, SLSI_MLME, "ch_lis_len:%d\n", ch_list_len);
+       for (i = 0; i < ch_list_len; i++) {
+               if (!ch_info[i].chan)
+                       continue;
+               adjacent_rssi = 0;   /* Assuming ch_list is in sorted order. */
+               for (j = -2; j <= 2; j++)
+                       if (i + j >= 0 && i + j < ch_list_len)
+                               adjacent_rssi += ch_info[i + j].rssi_factor;
+               ch_info[i].adj_rssi_factor = adjacent_rssi;
+               if (ch_info[i].num_bss_load_ap != 0) {
+                       ch_info[i].avg_chan_utilization = ch_info[i].total_chan_utilization /
+                                                         ch_info[i].num_bss_load_ap;
+                       if (ch_info[i].avg_chan_utilization < min_avg_chan_utilization_20) {
+                               min_avg_chan_utilization_20 = ch_info[i].avg_chan_utilization;
+                               ch_idx_min_load_20 = i;
+                       } else if (ch_info[i].avg_chan_utilization == min_avg_chan_utilization_20 &&
+                                  ch_info[i].num_ap < ch_info[ch_idx_min_load_20].num_ap) {
+                               ch_idx_min_load_20 = i;
+                       }
+                       none_bss_load = false;
+               } else {
+                       SLSI_DBG3(sdev, SLSI_MLME, "BSS load IE not found\n");
+                       all_bss_load = false;
+               }
+               if (adjacent_rssi < min_adjacent_rssi_20) {
+                       min_adjacent_rssi_20 = adjacent_rssi;
+                       ch_idx_min_rssi_20 = i;
+               } else if (adjacent_rssi == min_adjacent_rssi_20 &&
+                          ch_info[i].num_ap < ch_info[ch_idx_min_rssi_20].num_ap) {
+                       ch_idx_min_rssi_20 = i;
+               }
+               SLSI_DBG3(sdev, SLSI_MLME, "min rssi:%d min_rssi_idx:%d\n", min_adjacent_rssi_20, ch_idx_min_rssi_20);
+               SLSI_DBG3(sdev, SLSI_MLME, "num_ap:%d,chan:%d,total_util:%d,avg_util:%d,rssi_fac:%d,adj_rssi_fac:%d,"
+                         "bss_ap:%d\n", ch_info[i].num_ap, ch_info[i].chan, ch_info[i].total_chan_utilization,
+                         ch_info[i].avg_chan_utilization, ch_info[i].rssi_factor, ch_info[i].adj_rssi_factor,
+                         ch_info[i].num_bss_load_ap);
+       }
+
+       if (acs_selected_channels->ch_width == 20) {
+               if (all_bss_load)
+                       acs_selected_channels->pri_channel = ch_info[ch_idx_min_load_20].chan;
+               else
+                       acs_selected_channels->pri_channel = ch_info[ch_idx_min_rssi_20].chan;
+       } else if (acs_selected_channels->ch_width == 40) {
+               for (i = 0; i < ch_list_len; i++) {
+                       if (i + 4 >= ch_list_len || !ch_info[i + 4].chan || !ch_info[i].chan)
+                               continue;
+                       avg_load = ch_info[i].avg_chan_utilization + ch_info[i + 4].avg_chan_utilization;
+                       total_num_ap = ch_info[i].num_ap + ch_info[i + 4].num_ap;
+                       total_rssi = ch_info[i].adj_rssi_factor + ch_info[i + 4].adj_rssi_factor;
+
+                       if (avg_load < min_avg_chan_utilization) {
+                               min_avg_chan_utilization = avg_load;
+                               ch_idx_min_load = i;
+                       } else if (avg_load == min_avg_chan_utilization &&
+                                  total_num_ap < ch_info[ch_idx_min_load].num_ap +
+                                                 ch_info[ch_idx_min_load + 4].num_ap) {
+                               ch_idx_min_load = i;
+                       }
+                       if (total_rssi < min_adjacent_rssi) {
+                               min_adjacent_rssi = total_rssi;
+                               ch_idx_min_rssi = i;
+                       } else if (total_rssi == min_adjacent_rssi &&
+                                  total_num_ap < ch_info[ch_idx_min_rssi].num_ap +
+                                  ch_info[ch_idx_min_rssi + 4].num_ap) {
+                                  ch_idx_min_rssi = i;
+                       }
+               }
+               if (all_bss_load) {
+                       acs_selected_channels->pri_channel = ch_info[ch_idx_min_load].chan;
+                       acs_selected_channels->sec_channel = ch_info[ch_idx_min_load].chan + 4;
+               } else {
+                       acs_selected_channels->pri_channel = ch_info[ch_idx_min_rssi].chan;
+                       acs_selected_channels->sec_channel = ch_info[ch_idx_min_rssi].chan + 4;
+               }
+       }
+       return ret;
+}
+
+int slsi_is_40mhz_5gchan(u8 pri_channel, u8 sec_channel)
+{
+       int slsi_40mhz_chan[12] = {38, 46, 54, 62, 102, 110, 118, 126, 134, 142, 151, 159};
+       int i;
+
+       for (i = 0; i < 12; i++) {
+               if (pri_channel == slsi_40mhz_chan[i] - 2 && sec_channel == slsi_40mhz_chan[i] + 2)
+                       return 1;
+               else if (pri_channel < slsi_40mhz_chan[i])
+                       return 0;
+       }
+       return 0;
+}
+
+int slsi_is_80mhz_5gchan(u8 pri_channel, u8 last_channel)
+{
+       int slsi_80mhz_chan[6] = {42, 58, 106, 122, 138, 155};
+       int i;
+
+       for (i = 0; i < 6; i++) {
+               if (pri_channel == slsi_80mhz_chan[i] - 6 && last_channel == slsi_80mhz_chan[i] + 6)
+                       return 1;
+               else if (pri_channel < slsi_80mhz_chan[i])
+                       return 0;
+       }
+       return 0;
+}
+
+int slsi_set_5g_auto_channel(struct slsi_dev *sdev, struct netdev_vif  *ndev_vif,
+                            struct slsi_acs_selected_channels *acs_selected_channels,
+                            struct slsi_acs_chan_info *ch_info)
+{
+       int i = 0, avg_load, total_num_ap;
+       bool all_bss_load = true, none_bss_load = true;
+       int min_num_ap = INT_MAX, min_avg_chan_utilization = INT_MAX;
+       int ch_idx_min_load = 0, ch_idx_min_ap = 0;
+       int min_avg_chan_utilization_20 = INT_MAX, min_num_ap_20 = INT_MAX;
+       int ch_idx_min_load_20 = 0, ch_idx_min_ap_20 = 0;
+       int ret = 0;
+       int ch_list_len = ndev_vif->scan[SLSI_SCAN_HW_ID].acs_request->ch_list_len;
+
+       acs_selected_channels->ch_width = ndev_vif->scan[SLSI_SCAN_HW_ID].acs_request->ch_width;
+       acs_selected_channels->hw_mode = ndev_vif->scan[SLSI_SCAN_HW_ID].acs_request->hw_mode;
+
+       SLSI_DBG3(sdev, SLSI_MLME, "ch_lis_len:%d\n", ch_list_len);
+       for (i = 0; i < ch_list_len; i++) {
+               if (!ch_info[i].chan)
+                       continue;
+               if (ch_info[i].num_bss_load_ap != 0) {
+                       ch_info[i].avg_chan_utilization = ch_info[i].total_chan_utilization /
+                                                         ch_info[i].num_bss_load_ap;
+                       if (ch_info[i].avg_chan_utilization < min_avg_chan_utilization_20) {
+                               min_avg_chan_utilization_20 = ch_info[i].avg_chan_utilization;
+                               ch_idx_min_load_20 = i;
+                       } else if (ch_info[i].avg_chan_utilization == min_avg_chan_utilization_20 &&
+                                  ch_info[i].num_ap < ch_info[ch_idx_min_load_20].num_ap) {
+                               ch_idx_min_load_20 = i;
+                       }
+                       none_bss_load = false;
+               } else {
+                       if (ch_info[i].num_ap < min_num_ap_20) {
+                               min_num_ap_20 = ch_info[i].num_ap;
+                               ch_idx_min_ap_20 = i;
+                       }
+                       SLSI_DBG3(sdev, SLSI_MLME, "BSS load IE not found\n");
+                       ch_info[i].avg_chan_utilization = 128;
+                       all_bss_load = false;
+               }
+               SLSI_DBG3(sdev, SLSI_MLME, "num_ap:%d chan:%d, total_chan_util:%d, avg_chan_util:%d, bss_load_ap:%d\n",
+                         ch_info[i].num_ap, ch_info[i].chan, ch_info[i].total_chan_utilization,
+                         ch_info[i].avg_chan_utilization, ch_info[i].num_bss_load_ap);
+       }
+
+       if (acs_selected_channels->ch_width == 20) {
+               if (all_bss_load || min_avg_chan_utilization_20 < 128)
+                       acs_selected_channels->pri_channel = ch_info[ch_idx_min_load_20].chan;
+               else if (none_bss_load || min_avg_chan_utilization_20 >= 128)
+                       acs_selected_channels->pri_channel = ch_info[ch_idx_min_ap_20].chan;
+       } else if (acs_selected_channels->ch_width == 40) {
+               for (i = 0; i < ch_list_len; i++) {
+                       if (!ch_info[i].chan || i + 1 >= ch_list_len || !ch_info[i + 1].chan)
+                               continue;
+                       if (slsi_is_40mhz_5gchan(ch_info[i].chan, ch_info[i + 1].chan)) {
+                               avg_load = ch_info[i].avg_chan_utilization + ch_info[i + 1].avg_chan_utilization;
+                               total_num_ap = ch_info[i].num_ap + ch_info[i + 1].num_ap;
+                               if (avg_load < min_avg_chan_utilization) {
+                                       min_avg_chan_utilization = avg_load;
+                                       ch_idx_min_load = i;
+                               } else if (avg_load == min_avg_chan_utilization && total_num_ap <
+                                          ch_info[ch_idx_min_load].num_ap + ch_info[ch_idx_min_load + 1].num_ap) {
+                                       ch_idx_min_load = i;
+                               }
+                               if (total_num_ap < min_num_ap) {
+                                       min_num_ap = total_num_ap;
+                                       ch_idx_min_ap = i;
+                               }
+                       } else {
+                               SLSI_DBG3(sdev, SLSI_MLME, "Invalid channels: %d, %d\n", ch_info[i].chan,
+                                         ch_info[i + 1].chan); /*will remove after testing */
+                       }
+               }
+               if (all_bss_load || min_avg_chan_utilization <= 256) {
+                       acs_selected_channels->pri_channel = ch_info[ch_idx_min_load].chan;
+                       acs_selected_channels->sec_channel = ch_info[ch_idx_min_load + 1].chan;
+               } else if (none_bss_load || min_avg_chan_utilization > 256) {
+                       acs_selected_channels->pri_channel = ch_info[ch_idx_min_ap].chan;
+                       acs_selected_channels->sec_channel = ch_info[ch_idx_min_ap + 1].chan;
+               }
+       } else if (acs_selected_channels->ch_width == 80) {
+               for (i = 0; i < ch_list_len; i++) {
+                       if (i + 3 >= ch_list_len)
+                               continue;
+                       if (!ch_info[i].chan || !ch_info[i + 1].chan || !ch_info[i + 2].chan || !ch_info[i + 3].chan)
+                               continue;
+                       if (slsi_is_80mhz_5gchan(ch_info[i].chan, ch_info[i + 3].chan)) {
+                               avg_load = ch_info[i].avg_chan_utilization + ch_info[i + 1].avg_chan_utilization +
+                                          ch_info[i + 2].avg_chan_utilization + ch_info[i + 3].avg_chan_utilization;
+                               total_num_ap = ch_info[i].num_ap + ch_info[i + 1].num_ap + ch_info[i + 2].num_ap +
+                                              ch_info[i + 3].num_ap;
+                               if (avg_load < min_avg_chan_utilization) {
+                                       min_avg_chan_utilization = avg_load;
+                                       ch_idx_min_load = i;
+                               } else if (avg_load == min_avg_chan_utilization && total_num_ap <
+                                          (ch_info[ch_idx_min_load].num_ap + ch_info[ch_idx_min_load + 1].num_ap +
+                                           ch_info[ch_idx_min_load + 2].num_ap +
+                                           ch_info[ch_idx_min_load + 3].num_ap)) {
+                                       ch_idx_min_load = i;
+                               }
+                               if (total_num_ap < min_num_ap) {
+                                       min_num_ap = total_num_ap;
+                                       ch_idx_min_ap = i;
+                               }
+                       } else {
+                               SLSI_DBG3(sdev, SLSI_MLME, "Invalid channels: %d, %d\n", ch_info[i].chan,
+                                         ch_info[i + 3].chan);      /*will remove after testing */
+                       }
+               }
+               if (all_bss_load || min_avg_chan_utilization <= 512) {
+                       acs_selected_channels->pri_channel = ch_info[ch_idx_min_load].chan;
+                       acs_selected_channels->vht_seg0_center_ch = ch_info[ch_idx_min_load].chan + 6;
+               } else if (none_bss_load || min_avg_chan_utilization > 512) {
+                       acs_selected_channels->pri_channel = ch_info[ch_idx_min_ap].chan;
+                       acs_selected_channels->vht_seg0_center_ch = ch_info[ch_idx_min_ap].chan + 6;
+               }
+       }
+       return ret;
+}
+
+struct slsi_acs_chan_info *slsi_acs_scan_results(struct slsi_dev *sdev, struct netdev_vif  *ndev_vif, u16 scan_id)
+{
+       struct sk_buff *scan_res;
+       struct sk_buff *unique_scan;
+       struct sk_buff_head unique_scan_results;
+       struct slsi_acs_chan_info *ch_info = ndev_vif->scan[SLSI_SCAN_HW_ID].acs_request->acs_chan_info;
+
+       SLSI_DBG3(sdev, SLSI_MLME, "Received acs_results\n");
+       skb_queue_head_init(&unique_scan_results);
+       SLSI_MUTEX_LOCK(ndev_vif->scan_result_mutex);
+       scan_res = slsi_dequeue_cached_scan_result(&ndev_vif->scan[SLSI_SCAN_HW_ID], NULL);
+
+       while (scan_res) {
+               struct ieee80211_mgmt *mgmt = fapi_get_mgmt(scan_res);
+               size_t                mgmt_len = fapi_get_mgmtlen(scan_res);
+               struct ieee80211_channel *scan_channel;
+               int idx = 0;
+               const u8 *ie_data;
+               const u8 *ie;
+               int ie_len;
+               int ch_util = 128;
+               /* ieee80211_mgmt structure is similar for Probe Response and Beacons */
+               size_t   ies_len = mgmt_len - offsetof(struct ieee80211_mgmt, u.beacon.variable);
+               /* make sure this BSSID has not already been used */
+               skb_queue_walk(&unique_scan_results, unique_scan) {
+                       struct ieee80211_mgmt *unique_mgmt = fapi_get_mgmt(unique_scan);
+
+                       if (compare_ether_addr(mgmt->bssid, unique_mgmt->bssid) == 0)
+                               goto next_scan;
+               }
+               slsi_skb_queue_head(&unique_scan_results, scan_res);
+               scan_channel = slsi_find_scan_channel(sdev, mgmt, mgmt_len,
+                                                     fapi_get_u16(scan_res, u.mlme_scan_ind.channel_frequency) / 2);
+               if (!scan_channel)
+                       goto next_scan;
+               SLSI_DBG3(sdev, SLSI_MLME, "scan result (scan_id:%d, %pM, channel:%d, rssi:%d, ie_len = %zu)\n",
+                         fapi_get_u16(scan_res, u.mlme_scan_ind.scan_id),
+                         fapi_get_mgmt(scan_res)->bssid, scan_channel->hw_value,
+                         fapi_get_s16(scan_res, u.mlme_scan_ind.rssi),
+                         ies_len);
+
+               idx = slsi_find_chan_idx(scan_channel->hw_value, ndev_vif->scan[SLSI_SCAN_HW_ID].acs_request->hw_mode);
+               SLSI_DBG3(sdev, SLSI_MLME, "chan_idx:%d chan_value: %d\n", idx, ch_info[idx].chan);
+               if (ch_info[idx].chan) {
+                       ch_info[idx].num_ap += 1;
+                       ie = cfg80211_find_ie(WLAN_EID_QBSS_LOAD, mgmt->u.beacon.variable, ies_len);
+                       if (ie) {
+                               ie_len = ie[1];
+                               ie_data = &ie[2];
+                               if (ie_len >= 3) {
+                                       ch_util = ie_data[2];
+                                       ch_info[idx].num_bss_load_ap += 1;
+                                       ch_info[idx].total_chan_utilization += ch_util;
+                               }
+                       }
+                       if (idx == scan_channel->hw_value - 1)   /*if 2.4GHZ channel */
+                               ch_info[idx].rssi_factor += 10 ^ (fapi_get_s16(scan_res,
+                                                                 u.mlme_scan_ind.rssi) / 10) * ch_util;
+               } else {
+                       goto next_scan;
+               }
+next_scan:
+               scan_res = slsi_dequeue_cached_scan_result(&ndev_vif->scan[scan_id], NULL);
+       }
+       SLSI_MUTEX_UNLOCK(ndev_vif->scan_result_mutex);
+       slsi_skb_queue_purge(&unique_scan_results);
+       SLSI_DBG3(sdev, SLSI_MLME, "slsi_acs_scan_results Received end point\n");      /*will remove after testing */
+       return ch_info;
+}
+
+void slsi_acs_scan_complete(struct slsi_dev *sdev, struct netdev_vif *ndev_vif,  u16 scan_id)
+{
+       struct slsi_acs_selected_channels acs_selected_channels;
+       struct slsi_acs_chan_info *ch_info;
+       int r = 0;
+
+       memset(&acs_selected_channels, 0, sizeof(acs_selected_channels));
+       ch_info = slsi_acs_scan_results(sdev, ndev_vif, scan_id);
+       if (ndev_vif->scan[SLSI_SCAN_HW_ID].acs_request->hw_mode == SLSI_ACS_MODE_IEEE80211A)
+               r = slsi_set_5g_auto_channel(sdev, ndev_vif, &acs_selected_channels, ch_info);
+       else if (ndev_vif->scan[SLSI_SCAN_HW_ID].acs_request->hw_mode == SLSI_ACS_MODE_IEEE80211B ||
+                ndev_vif->scan[SLSI_SCAN_HW_ID].acs_request->hw_mode == SLSI_ACS_MODE_IEEE80211G)
+               r = slsi_set_2g_auto_channel(sdev, ndev_vif, &acs_selected_channels, ch_info);
+       else
+               r = -EINVAL;
+       if (!r) {
+               r = slsi_send_acs_event(sdev, acs_selected_channels);
+               if (r != 0)
+                       SLSI_ERR(sdev, "Could not send ACS vendor event up\n");
+       } else {
+               SLSI_ERR(sdev, "set_auto_channel failed: %d\n", r);
+       }
+       sdev->acs_channel_switched = true;
+       kfree(ndev_vif->scan[SLSI_SCAN_HW_ID].acs_request);
+       if (ndev_vif->scan[SLSI_SCAN_HW_ID].acs_request)
+               ndev_vif->scan[SLSI_SCAN_HW_ID].acs_request = NULL;
+}
+
 void slsi_rx_scan_done_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb)
 {
        u16               scan_id = fapi_get_u16(skb, u.mlme_scan_done_ind.scan_id);
@@ -542,6 +919,7 @@ void slsi_rx_scan_done_ind(struct slsi_dev *sdev, struct net_device *dev, struct
 
        SLSI_MUTEX_LOCK(ndev_vif->vif_mutex);
        SLSI_MUTEX_LOCK(ndev_vif->scan_mutex);
+       SLSI_NET_DBG3(dev, SLSI_GSCAN, "slsi_rx_scan_done_ind Received scan_id:%#x\n", scan_id);
 
 #ifdef CONFIG_SCSC_WLAN_GSCAN_ENABLE
        if (slsi_is_gscan_id(scan_id)) {
@@ -556,10 +934,13 @@ void slsi_rx_scan_done_ind(struct slsi_dev *sdev, struct net_device *dev, struct
 #endif
        scan_id = (scan_id & 0xFF);
 
-       if (scan_id == SLSI_SCAN_HW_ID && ndev_vif->scan[SLSI_SCAN_HW_ID].scan_req)
+       if (scan_id == SLSI_SCAN_HW_ID && (ndev_vif->scan[SLSI_SCAN_HW_ID].scan_req ||
+                                          ndev_vif->scan[SLSI_SCAN_HW_ID].acs_request))
                cancel_delayed_work(&ndev_vif->scan_timeout_work);
-
-       slsi_scan_complete(sdev, dev, scan_id, false);
+       if (ndev_vif->scan[SLSI_SCAN_HW_ID].acs_request)
+               slsi_acs_scan_complete(sdev, ndev_vif, scan_id);
+       else
+               slsi_scan_complete(sdev, dev, scan_id, false);
 
        SLSI_MUTEX_UNLOCK(ndev_vif->scan_mutex);
        SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex);