linux/drivers/net/wireless/ath/wcn36xx/main.c
<<
>>
Prefs
   1/*
   2 * Copyright (c) 2013 Eugene Krasnikov <k.eugene.e@gmail.com>
   3 *
   4 * Permission to use, copy, modify, and/or distribute this software for any
   5 * purpose with or without fee is hereby granted, provided that the above
   6 * copyright notice and this permission notice appear in all copies.
   7 *
   8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
   9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
  11 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
  13 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
  14 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  15 */
  16
  17#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  18
  19#include <linux/module.h>
  20#include <linux/firmware.h>
  21#include <linux/platform_device.h>
  22#include "wcn36xx.h"
  23
  24unsigned int wcn36xx_dbg_mask;
  25module_param_named(debug_mask, wcn36xx_dbg_mask, uint, 0644);
  26MODULE_PARM_DESC(debug_mask, "Debugging mask");
  27
  28#define CHAN2G(_freq, _idx) { \
  29        .band = IEEE80211_BAND_2GHZ, \
  30        .center_freq = (_freq), \
  31        .hw_value = (_idx), \
  32        .max_power = 25, \
  33}
  34
  35#define CHAN5G(_freq, _idx) { \
  36        .band = IEEE80211_BAND_5GHZ, \
  37        .center_freq = (_freq), \
  38        .hw_value = (_idx), \
  39        .max_power = 25, \
  40}
  41
  42/* The wcn firmware expects channel values to matching
  43 * their mnemonic values. So use these for .hw_value. */
  44static struct ieee80211_channel wcn_2ghz_channels[] = {
  45        CHAN2G(2412, 1), /* Channel 1 */
  46        CHAN2G(2417, 2), /* Channel 2 */
  47        CHAN2G(2422, 3), /* Channel 3 */
  48        CHAN2G(2427, 4), /* Channel 4 */
  49        CHAN2G(2432, 5), /* Channel 5 */
  50        CHAN2G(2437, 6), /* Channel 6 */
  51        CHAN2G(2442, 7), /* Channel 7 */
  52        CHAN2G(2447, 8), /* Channel 8 */
  53        CHAN2G(2452, 9), /* Channel 9 */
  54        CHAN2G(2457, 10), /* Channel 10 */
  55        CHAN2G(2462, 11), /* Channel 11 */
  56        CHAN2G(2467, 12), /* Channel 12 */
  57        CHAN2G(2472, 13), /* Channel 13 */
  58        CHAN2G(2484, 14)  /* Channel 14 */
  59
  60};
  61
  62static struct ieee80211_channel wcn_5ghz_channels[] = {
  63        CHAN5G(5180, 36),
  64        CHAN5G(5200, 40),
  65        CHAN5G(5220, 44),
  66        CHAN5G(5240, 48),
  67        CHAN5G(5260, 52),
  68        CHAN5G(5280, 56),
  69        CHAN5G(5300, 60),
  70        CHAN5G(5320, 64),
  71        CHAN5G(5500, 100),
  72        CHAN5G(5520, 104),
  73        CHAN5G(5540, 108),
  74        CHAN5G(5560, 112),
  75        CHAN5G(5580, 116),
  76        CHAN5G(5600, 120),
  77        CHAN5G(5620, 124),
  78        CHAN5G(5640, 128),
  79        CHAN5G(5660, 132),
  80        CHAN5G(5700, 140),
  81        CHAN5G(5745, 149),
  82        CHAN5G(5765, 153),
  83        CHAN5G(5785, 157),
  84        CHAN5G(5805, 161),
  85        CHAN5G(5825, 165)
  86};
  87
  88#define RATE(_bitrate, _hw_rate, _flags) { \
  89        .bitrate        = (_bitrate),                   \
  90        .flags          = (_flags),                     \
  91        .hw_value       = (_hw_rate),                   \
  92        .hw_value_short = (_hw_rate)  \
  93}
  94
  95static struct ieee80211_rate wcn_2ghz_rates[] = {
  96        RATE(10, HW_RATE_INDEX_1MBPS, 0),
  97        RATE(20, HW_RATE_INDEX_2MBPS, IEEE80211_RATE_SHORT_PREAMBLE),
  98        RATE(55, HW_RATE_INDEX_5_5MBPS, IEEE80211_RATE_SHORT_PREAMBLE),
  99        RATE(110, HW_RATE_INDEX_11MBPS, IEEE80211_RATE_SHORT_PREAMBLE),
 100        RATE(60, HW_RATE_INDEX_6MBPS, 0),
 101        RATE(90, HW_RATE_INDEX_9MBPS, 0),
 102        RATE(120, HW_RATE_INDEX_12MBPS, 0),
 103        RATE(180, HW_RATE_INDEX_18MBPS, 0),
 104        RATE(240, HW_RATE_INDEX_24MBPS, 0),
 105        RATE(360, HW_RATE_INDEX_36MBPS, 0),
 106        RATE(480, HW_RATE_INDEX_48MBPS, 0),
 107        RATE(540, HW_RATE_INDEX_54MBPS, 0)
 108};
 109
 110static struct ieee80211_rate wcn_5ghz_rates[] = {
 111        RATE(60, HW_RATE_INDEX_6MBPS, 0),
 112        RATE(90, HW_RATE_INDEX_9MBPS, 0),
 113        RATE(120, HW_RATE_INDEX_12MBPS, 0),
 114        RATE(180, HW_RATE_INDEX_18MBPS, 0),
 115        RATE(240, HW_RATE_INDEX_24MBPS, 0),
 116        RATE(360, HW_RATE_INDEX_36MBPS, 0),
 117        RATE(480, HW_RATE_INDEX_48MBPS, 0),
 118        RATE(540, HW_RATE_INDEX_54MBPS, 0)
 119};
 120
 121static struct ieee80211_supported_band wcn_band_2ghz = {
 122        .channels       = wcn_2ghz_channels,
 123        .n_channels     = ARRAY_SIZE(wcn_2ghz_channels),
 124        .bitrates       = wcn_2ghz_rates,
 125        .n_bitrates     = ARRAY_SIZE(wcn_2ghz_rates),
 126        .ht_cap         = {
 127                .cap =  IEEE80211_HT_CAP_GRN_FLD |
 128                        IEEE80211_HT_CAP_SGI_20 |
 129                        IEEE80211_HT_CAP_DSSSCCK40 |
 130                        IEEE80211_HT_CAP_LSIG_TXOP_PROT,
 131                .ht_supported = true,
 132                .ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K,
 133                .ampdu_density = IEEE80211_HT_MPDU_DENSITY_16,
 134                .mcs = {
 135                        .rx_mask = { 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
 136                        .rx_highest = cpu_to_le16(72),
 137                        .tx_params = IEEE80211_HT_MCS_TX_DEFINED,
 138                }
 139        }
 140};
 141
 142static struct ieee80211_supported_band wcn_band_5ghz = {
 143        .channels       = wcn_5ghz_channels,
 144        .n_channels     = ARRAY_SIZE(wcn_5ghz_channels),
 145        .bitrates       = wcn_5ghz_rates,
 146        .n_bitrates     = ARRAY_SIZE(wcn_5ghz_rates),
 147        .ht_cap         = {
 148                .cap =  IEEE80211_HT_CAP_GRN_FLD |
 149                        IEEE80211_HT_CAP_SGI_20 |
 150                        IEEE80211_HT_CAP_DSSSCCK40 |
 151                        IEEE80211_HT_CAP_LSIG_TXOP_PROT |
 152                        IEEE80211_HT_CAP_SGI_40 |
 153                        IEEE80211_HT_CAP_SUP_WIDTH_20_40,
 154                .ht_supported = true,
 155                .ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K,
 156                .ampdu_density = IEEE80211_HT_MPDU_DENSITY_16,
 157                .mcs = {
 158                        .rx_mask = { 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
 159                        .rx_highest = cpu_to_le16(72),
 160                        .tx_params = IEEE80211_HT_MCS_TX_DEFINED,
 161                }
 162        }
 163};
 164
 165#ifdef CONFIG_PM
 166
 167static const struct wiphy_wowlan_support wowlan_support = {
 168        .flags = WIPHY_WOWLAN_ANY
 169};
 170
 171#endif
 172
 173static inline u8 get_sta_index(struct ieee80211_vif *vif,
 174                               struct wcn36xx_sta *sta_priv)
 175{
 176        return NL80211_IFTYPE_STATION == vif->type ?
 177               sta_priv->bss_sta_index :
 178               sta_priv->sta_index;
 179}
 180
 181static const char * const wcn36xx_caps_names[] = {
 182        "MCC",                          /* 0 */
 183        "P2P",                          /* 1 */
 184        "DOT11AC",                      /* 2 */
 185        "SLM_SESSIONIZATION",           /* 3 */
 186        "DOT11AC_OPMODE",               /* 4 */
 187        "SAP32STA",                     /* 5 */
 188        "TDLS",                         /* 6 */
 189        "P2P_GO_NOA_DECOUPLE_INIT_SCAN",/* 7 */
 190        "WLANACTIVE_OFFLOAD",           /* 8 */
 191        "BEACON_OFFLOAD",               /* 9 */
 192        "SCAN_OFFLOAD",                 /* 10 */
 193        "ROAM_OFFLOAD",                 /* 11 */
 194        "BCN_MISS_OFFLOAD",             /* 12 */
 195        "STA_POWERSAVE",                /* 13 */
 196        "STA_ADVANCED_PWRSAVE",         /* 14 */
 197        "AP_UAPSD",                     /* 15 */
 198        "AP_DFS",                       /* 16 */
 199        "BLOCKACK",                     /* 17 */
 200        "PHY_ERR",                      /* 18 */
 201        "BCN_FILTER",                   /* 19 */
 202        "RTT",                          /* 20 */
 203        "RATECTRL",                     /* 21 */
 204        "WOW"                           /* 22 */
 205};
 206
 207static const char *wcn36xx_get_cap_name(enum place_holder_in_cap_bitmap x)
 208{
 209        if (x >= ARRAY_SIZE(wcn36xx_caps_names))
 210                return "UNKNOWN";
 211        return wcn36xx_caps_names[x];
 212}
 213
 214static void wcn36xx_feat_caps_info(struct wcn36xx *wcn)
 215{
 216        int i;
 217
 218        for (i = 0; i < MAX_FEATURE_SUPPORTED; i++) {
 219                if (get_feat_caps(wcn->fw_feat_caps, i))
 220                        wcn36xx_info("FW Cap %s\n", wcn36xx_get_cap_name(i));
 221        }
 222}
 223
 224static void wcn36xx_detect_chip_version(struct wcn36xx *wcn)
 225{
 226        if (get_feat_caps(wcn->fw_feat_caps, DOT11AC)) {
 227                wcn36xx_info("Chip is 3680\n");
 228                wcn->chip_version = WCN36XX_CHIP_3680;
 229        } else {
 230                wcn36xx_info("Chip is 3660\n");
 231                wcn->chip_version = WCN36XX_CHIP_3660;
 232        }
 233}
 234
 235static int wcn36xx_start(struct ieee80211_hw *hw)
 236{
 237        struct wcn36xx *wcn = hw->priv;
 238        int ret;
 239
 240        wcn36xx_dbg(WCN36XX_DBG_MAC, "mac start\n");
 241
 242        /* SMD initialization */
 243        ret = wcn36xx_smd_open(wcn);
 244        if (ret) {
 245                wcn36xx_err("Failed to open smd channel: %d\n", ret);
 246                goto out_err;
 247        }
 248
 249        /* Allocate memory pools for Mgmt BD headers and Data BD headers */
 250        ret = wcn36xx_dxe_allocate_mem_pools(wcn);
 251        if (ret) {
 252                wcn36xx_err("Failed to alloc DXE mempool: %d\n", ret);
 253                goto out_smd_close;
 254        }
 255
 256        ret = wcn36xx_dxe_alloc_ctl_blks(wcn);
 257        if (ret) {
 258                wcn36xx_err("Failed to alloc DXE ctl blocks: %d\n", ret);
 259                goto out_free_dxe_pool;
 260        }
 261
 262        wcn->hal_buf = kmalloc(WCN36XX_HAL_BUF_SIZE, GFP_KERNEL);
 263        if (!wcn->hal_buf) {
 264                wcn36xx_err("Failed to allocate smd buf\n");
 265                ret = -ENOMEM;
 266                goto out_free_dxe_ctl;
 267        }
 268
 269        ret = wcn36xx_smd_load_nv(wcn);
 270        if (ret) {
 271                wcn36xx_err("Failed to push NV to chip\n");
 272                goto out_free_smd_buf;
 273        }
 274
 275        ret = wcn36xx_smd_start(wcn);
 276        if (ret) {
 277                wcn36xx_err("Failed to start chip\n");
 278                goto out_free_smd_buf;
 279        }
 280
 281        if (!wcn36xx_is_fw_version(wcn, 1, 2, 2, 24)) {
 282                ret = wcn36xx_smd_feature_caps_exchange(wcn);
 283                if (ret)
 284                        wcn36xx_warn("Exchange feature caps failed\n");
 285                else
 286                        wcn36xx_feat_caps_info(wcn);
 287        }
 288
 289        wcn36xx_detect_chip_version(wcn);
 290
 291        /* DMA channel initialization */
 292        ret = wcn36xx_dxe_init(wcn);
 293        if (ret) {
 294                wcn36xx_err("DXE init failed\n");
 295                goto out_smd_stop;
 296        }
 297
 298        wcn36xx_debugfs_init(wcn);
 299
 300        INIT_LIST_HEAD(&wcn->vif_list);
 301        spin_lock_init(&wcn->dxe_lock);
 302
 303        return 0;
 304
 305out_smd_stop:
 306        wcn36xx_smd_stop(wcn);
 307out_free_smd_buf:
 308        kfree(wcn->hal_buf);
 309out_free_dxe_pool:
 310        wcn36xx_dxe_free_mem_pools(wcn);
 311out_free_dxe_ctl:
 312        wcn36xx_dxe_free_ctl_blks(wcn);
 313out_smd_close:
 314        wcn36xx_smd_close(wcn);
 315out_err:
 316        return ret;
 317}
 318
 319static void wcn36xx_stop(struct ieee80211_hw *hw)
 320{
 321        struct wcn36xx *wcn = hw->priv;
 322
 323        wcn36xx_dbg(WCN36XX_DBG_MAC, "mac stop\n");
 324
 325        wcn36xx_debugfs_exit(wcn);
 326        wcn36xx_smd_stop(wcn);
 327        wcn36xx_dxe_deinit(wcn);
 328        wcn36xx_smd_close(wcn);
 329
 330        wcn36xx_dxe_free_mem_pools(wcn);
 331        wcn36xx_dxe_free_ctl_blks(wcn);
 332
 333        kfree(wcn->hal_buf);
 334}
 335
 336static int wcn36xx_config(struct ieee80211_hw *hw, u32 changed)
 337{
 338        struct wcn36xx *wcn = hw->priv;
 339        struct ieee80211_vif *vif = NULL;
 340        struct wcn36xx_vif *tmp;
 341
 342        wcn36xx_dbg(WCN36XX_DBG_MAC, "mac config changed 0x%08x\n", changed);
 343
 344        if (changed & IEEE80211_CONF_CHANGE_CHANNEL) {
 345                int ch = WCN36XX_HW_CHANNEL(wcn);
 346                wcn36xx_dbg(WCN36XX_DBG_MAC, "wcn36xx_config channel switch=%d\n",
 347                            ch);
 348                list_for_each_entry(tmp, &wcn->vif_list, list) {
 349                        vif = container_of((void *)tmp,
 350                                           struct ieee80211_vif,
 351                                           drv_priv);
 352                        wcn36xx_smd_switch_channel(wcn, vif, ch);
 353                }
 354        }
 355
 356        return 0;
 357}
 358
 359#define WCN36XX_SUPPORTED_FILTERS (0)
 360
 361static void wcn36xx_configure_filter(struct ieee80211_hw *hw,
 362                                     unsigned int changed,
 363                                     unsigned int *total, u64 multicast)
 364{
 365        wcn36xx_dbg(WCN36XX_DBG_MAC, "mac configure filter\n");
 366
 367        *total &= WCN36XX_SUPPORTED_FILTERS;
 368}
 369
 370static void wcn36xx_tx(struct ieee80211_hw *hw,
 371                       struct ieee80211_tx_control *control,
 372                       struct sk_buff *skb)
 373{
 374        struct wcn36xx *wcn = hw->priv;
 375        struct wcn36xx_sta *sta_priv = NULL;
 376
 377        if (control->sta)
 378                sta_priv = (struct wcn36xx_sta *)control->sta->drv_priv;
 379
 380        if (wcn36xx_start_tx(wcn, sta_priv, skb))
 381                ieee80211_free_txskb(wcn->hw, skb);
 382}
 383
 384static int wcn36xx_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
 385                           struct ieee80211_vif *vif,
 386                           struct ieee80211_sta *sta,
 387                           struct ieee80211_key_conf *key_conf)
 388{
 389        struct wcn36xx *wcn = hw->priv;
 390        struct wcn36xx_vif *vif_priv = (struct wcn36xx_vif *)vif->drv_priv;
 391        struct wcn36xx_sta *sta_priv = vif_priv->sta;
 392        int ret = 0;
 393        u8 key[WLAN_MAX_KEY_LEN];
 394
 395        wcn36xx_dbg(WCN36XX_DBG_MAC, "mac80211 set key\n");
 396        wcn36xx_dbg(WCN36XX_DBG_MAC, "Key: cmd=0x%x algo:0x%x, id:%d, len:%d flags 0x%x\n",
 397                    cmd, key_conf->cipher, key_conf->keyidx,
 398                    key_conf->keylen, key_conf->flags);
 399        wcn36xx_dbg_dump(WCN36XX_DBG_MAC, "KEY: ",
 400                         key_conf->key,
 401                         key_conf->keylen);
 402
 403        switch (key_conf->cipher) {
 404        case WLAN_CIPHER_SUITE_WEP40:
 405                vif_priv->encrypt_type = WCN36XX_HAL_ED_WEP40;
 406                break;
 407        case WLAN_CIPHER_SUITE_WEP104:
 408                vif_priv->encrypt_type = WCN36XX_HAL_ED_WEP40;
 409                break;
 410        case WLAN_CIPHER_SUITE_CCMP:
 411                vif_priv->encrypt_type = WCN36XX_HAL_ED_CCMP;
 412                break;
 413        case WLAN_CIPHER_SUITE_TKIP:
 414                vif_priv->encrypt_type = WCN36XX_HAL_ED_TKIP;
 415                break;
 416        default:
 417                wcn36xx_err("Unsupported key type 0x%x\n",
 418                              key_conf->cipher);
 419                ret = -EOPNOTSUPP;
 420                goto out;
 421        }
 422
 423        switch (cmd) {
 424        case SET_KEY:
 425                if (WCN36XX_HAL_ED_TKIP == vif_priv->encrypt_type) {
 426                        /*
 427                         * Supplicant is sending key in the wrong order:
 428                         * Temporal Key (16 b) - TX MIC (8 b) - RX MIC (8 b)
 429                         * but HW expects it to be in the order as described in
 430                         * IEEE 802.11 spec (see chapter 11.7) like this:
 431                         * Temporal Key (16 b) - RX MIC (8 b) - TX MIC (8 b)
 432                         */
 433                        memcpy(key, key_conf->key, 16);
 434                        memcpy(key + 16, key_conf->key + 24, 8);
 435                        memcpy(key + 24, key_conf->key + 16, 8);
 436                } else {
 437                        memcpy(key, key_conf->key, key_conf->keylen);
 438                }
 439
 440                if (IEEE80211_KEY_FLAG_PAIRWISE & key_conf->flags) {
 441                        sta_priv->is_data_encrypted = true;
 442                        /* Reconfigure bss with encrypt_type */
 443                        if (NL80211_IFTYPE_STATION == vif->type)
 444                                wcn36xx_smd_config_bss(wcn,
 445                                                       vif,
 446                                                       sta,
 447                                                       sta->addr,
 448                                                       true);
 449
 450                        wcn36xx_smd_set_stakey(wcn,
 451                                vif_priv->encrypt_type,
 452                                key_conf->keyidx,
 453                                key_conf->keylen,
 454                                key,
 455                                get_sta_index(vif, sta_priv));
 456                } else {
 457                        wcn36xx_smd_set_bsskey(wcn,
 458                                vif_priv->encrypt_type,
 459                                key_conf->keyidx,
 460                                key_conf->keylen,
 461                                key);
 462                        if ((WLAN_CIPHER_SUITE_WEP40 == key_conf->cipher) ||
 463                            (WLAN_CIPHER_SUITE_WEP104 == key_conf->cipher)) {
 464                                sta_priv->is_data_encrypted = true;
 465                                wcn36xx_smd_set_stakey(wcn,
 466                                        vif_priv->encrypt_type,
 467                                        key_conf->keyidx,
 468                                        key_conf->keylen,
 469                                        key,
 470                                        get_sta_index(vif, sta_priv));
 471                        }
 472                }
 473                break;
 474        case DISABLE_KEY:
 475                if (!(IEEE80211_KEY_FLAG_PAIRWISE & key_conf->flags)) {
 476                        wcn36xx_smd_remove_bsskey(wcn,
 477                                vif_priv->encrypt_type,
 478                                key_conf->keyidx);
 479                } else {
 480                        sta_priv->is_data_encrypted = false;
 481                        /* do not remove key if disassociated */
 482                        if (sta_priv->aid)
 483                                wcn36xx_smd_remove_stakey(wcn,
 484                                        vif_priv->encrypt_type,
 485                                        key_conf->keyidx,
 486                                        get_sta_index(vif, sta_priv));
 487                }
 488                break;
 489        default:
 490                wcn36xx_err("Unsupported key cmd 0x%x\n", cmd);
 491                ret = -EOPNOTSUPP;
 492                goto out;
 493        }
 494
 495out:
 496        return ret;
 497}
 498
 499static void wcn36xx_sw_scan_start(struct ieee80211_hw *hw,
 500                                  struct ieee80211_vif *vif,
 501                                  const u8 *mac_addr)
 502{
 503        struct wcn36xx *wcn = hw->priv;
 504
 505        wcn36xx_smd_init_scan(wcn, HAL_SYS_MODE_SCAN);
 506        wcn36xx_smd_start_scan(wcn);
 507}
 508
 509static void wcn36xx_sw_scan_complete(struct ieee80211_hw *hw,
 510                                     struct ieee80211_vif *vif)
 511{
 512        struct wcn36xx *wcn = hw->priv;
 513
 514        wcn36xx_smd_end_scan(wcn);
 515        wcn36xx_smd_finish_scan(wcn, HAL_SYS_MODE_SCAN);
 516}
 517
 518static void wcn36xx_update_allowed_rates(struct ieee80211_sta *sta,
 519                                         enum ieee80211_band band)
 520{
 521        int i, size;
 522        u16 *rates_table;
 523        struct wcn36xx_sta *sta_priv = (struct wcn36xx_sta *)sta->drv_priv;
 524        u32 rates = sta->supp_rates[band];
 525
 526        memset(&sta_priv->supported_rates, 0,
 527                sizeof(sta_priv->supported_rates));
 528        sta_priv->supported_rates.op_rate_mode = STA_11n;
 529
 530        size = ARRAY_SIZE(sta_priv->supported_rates.dsss_rates);
 531        rates_table = sta_priv->supported_rates.dsss_rates;
 532        if (band == IEEE80211_BAND_2GHZ) {
 533                for (i = 0; i < size; i++) {
 534                        if (rates & 0x01) {
 535                                rates_table[i] = wcn_2ghz_rates[i].hw_value;
 536                                rates = rates >> 1;
 537                        }
 538                }
 539        }
 540
 541        size = ARRAY_SIZE(sta_priv->supported_rates.ofdm_rates);
 542        rates_table = sta_priv->supported_rates.ofdm_rates;
 543        for (i = 0; i < size; i++) {
 544                if (rates & 0x01) {
 545                        rates_table[i] = wcn_5ghz_rates[i].hw_value;
 546                        rates = rates >> 1;
 547                }
 548        }
 549
 550        if (sta->ht_cap.ht_supported) {
 551                BUILD_BUG_ON(sizeof(sta->ht_cap.mcs.rx_mask) >
 552                        sizeof(sta_priv->supported_rates.supported_mcs_set));
 553                memcpy(sta_priv->supported_rates.supported_mcs_set,
 554                       sta->ht_cap.mcs.rx_mask,
 555                       sizeof(sta->ht_cap.mcs.rx_mask));
 556        }
 557}
 558void wcn36xx_set_default_rates(struct wcn36xx_hal_supported_rates *rates)
 559{
 560        u16 ofdm_rates[WCN36XX_HAL_NUM_OFDM_RATES] = {
 561                HW_RATE_INDEX_6MBPS,
 562                HW_RATE_INDEX_9MBPS,
 563                HW_RATE_INDEX_12MBPS,
 564                HW_RATE_INDEX_18MBPS,
 565                HW_RATE_INDEX_24MBPS,
 566                HW_RATE_INDEX_36MBPS,
 567                HW_RATE_INDEX_48MBPS,
 568                HW_RATE_INDEX_54MBPS
 569        };
 570        u16 dsss_rates[WCN36XX_HAL_NUM_DSSS_RATES] = {
 571                HW_RATE_INDEX_1MBPS,
 572                HW_RATE_INDEX_2MBPS,
 573                HW_RATE_INDEX_5_5MBPS,
 574                HW_RATE_INDEX_11MBPS
 575        };
 576
 577        rates->op_rate_mode = STA_11n;
 578        memcpy(rates->dsss_rates, dsss_rates,
 579                sizeof(*dsss_rates) * WCN36XX_HAL_NUM_DSSS_RATES);
 580        memcpy(rates->ofdm_rates, ofdm_rates,
 581                sizeof(*ofdm_rates) * WCN36XX_HAL_NUM_OFDM_RATES);
 582        rates->supported_mcs_set[0] = 0xFF;
 583}
 584static void wcn36xx_bss_info_changed(struct ieee80211_hw *hw,
 585                                     struct ieee80211_vif *vif,
 586                                     struct ieee80211_bss_conf *bss_conf,
 587                                     u32 changed)
 588{
 589        struct wcn36xx *wcn = hw->priv;
 590        struct sk_buff *skb = NULL;
 591        u16 tim_off, tim_len;
 592        enum wcn36xx_hal_link_state link_state;
 593        struct wcn36xx_vif *vif_priv = (struct wcn36xx_vif *)vif->drv_priv;
 594
 595        wcn36xx_dbg(WCN36XX_DBG_MAC, "mac bss info changed vif %p changed 0x%08x\n",
 596                    vif, changed);
 597
 598        if (changed & BSS_CHANGED_BEACON_INFO) {
 599                wcn36xx_dbg(WCN36XX_DBG_MAC,
 600                            "mac bss changed dtim period %d\n",
 601                            bss_conf->dtim_period);
 602
 603                vif_priv->dtim_period = bss_conf->dtim_period;
 604        }
 605
 606        if (changed & BSS_CHANGED_PS) {
 607                wcn36xx_dbg(WCN36XX_DBG_MAC,
 608                            "mac bss PS set %d\n",
 609                            bss_conf->ps);
 610                if (bss_conf->ps) {
 611                        wcn36xx_pmc_enter_bmps_state(wcn, vif);
 612                } else {
 613                        wcn36xx_pmc_exit_bmps_state(wcn, vif);
 614                }
 615        }
 616
 617        if (changed & BSS_CHANGED_BSSID) {
 618                wcn36xx_dbg(WCN36XX_DBG_MAC, "mac bss changed_bssid %pM\n",
 619                            bss_conf->bssid);
 620
 621                if (!is_zero_ether_addr(bss_conf->bssid)) {
 622                        vif_priv->is_joining = true;
 623                        vif_priv->bss_index = 0xff;
 624                        wcn36xx_smd_join(wcn, bss_conf->bssid,
 625                                         vif->addr, WCN36XX_HW_CHANNEL(wcn));
 626                        wcn36xx_smd_config_bss(wcn, vif, NULL,
 627                                               bss_conf->bssid, false);
 628                } else {
 629                        vif_priv->is_joining = false;
 630                        wcn36xx_smd_delete_bss(wcn, vif);
 631                }
 632        }
 633
 634        if (changed & BSS_CHANGED_SSID) {
 635                wcn36xx_dbg(WCN36XX_DBG_MAC,
 636                            "mac bss changed ssid\n");
 637                wcn36xx_dbg_dump(WCN36XX_DBG_MAC, "ssid ",
 638                                 bss_conf->ssid, bss_conf->ssid_len);
 639
 640                vif_priv->ssid.length = bss_conf->ssid_len;
 641                memcpy(&vif_priv->ssid.ssid,
 642                       bss_conf->ssid,
 643                       bss_conf->ssid_len);
 644        }
 645
 646        if (changed & BSS_CHANGED_ASSOC) {
 647                vif_priv->is_joining = false;
 648                if (bss_conf->assoc) {
 649                        struct ieee80211_sta *sta;
 650                        struct wcn36xx_sta *sta_priv;
 651
 652                        wcn36xx_dbg(WCN36XX_DBG_MAC,
 653                                    "mac assoc bss %pM vif %pM AID=%d\n",
 654                                     bss_conf->bssid,
 655                                     vif->addr,
 656                                     bss_conf->aid);
 657
 658                        rcu_read_lock();
 659                        sta = ieee80211_find_sta(vif, bss_conf->bssid);
 660                        if (!sta) {
 661                                wcn36xx_err("sta %pM is not found\n",
 662                                              bss_conf->bssid);
 663                                rcu_read_unlock();
 664                                goto out;
 665                        }
 666                        sta_priv = (struct wcn36xx_sta *)sta->drv_priv;
 667
 668                        wcn36xx_update_allowed_rates(sta, WCN36XX_BAND(wcn));
 669
 670                        wcn36xx_smd_set_link_st(wcn, bss_conf->bssid,
 671                                vif->addr,
 672                                WCN36XX_HAL_LINK_POSTASSOC_STATE);
 673                        wcn36xx_smd_config_bss(wcn, vif, sta,
 674                                               bss_conf->bssid,
 675                                               true);
 676                        sta_priv->aid = bss_conf->aid;
 677                        /*
 678                         * config_sta must be called from  because this is the
 679                         * place where AID is available.
 680                         */
 681                        wcn36xx_smd_config_sta(wcn, vif, sta);
 682                        rcu_read_unlock();
 683                } else {
 684                        wcn36xx_dbg(WCN36XX_DBG_MAC,
 685                                    "disassociated bss %pM vif %pM AID=%d\n",
 686                                    bss_conf->bssid,
 687                                    vif->addr,
 688                                    bss_conf->aid);
 689                        wcn36xx_smd_set_link_st(wcn,
 690                                                bss_conf->bssid,
 691                                                vif->addr,
 692                                                WCN36XX_HAL_LINK_IDLE_STATE);
 693                }
 694        }
 695
 696        if (changed & BSS_CHANGED_AP_PROBE_RESP) {
 697                wcn36xx_dbg(WCN36XX_DBG_MAC, "mac bss changed ap probe resp\n");
 698                skb = ieee80211_proberesp_get(hw, vif);
 699                if (!skb) {
 700                        wcn36xx_err("failed to alloc probereq skb\n");
 701                        goto out;
 702                }
 703
 704                wcn36xx_smd_update_proberesp_tmpl(wcn, vif, skb);
 705                dev_kfree_skb(skb);
 706        }
 707
 708        if (changed & BSS_CHANGED_BEACON_ENABLED ||
 709            changed & BSS_CHANGED_BEACON) {
 710                wcn36xx_dbg(WCN36XX_DBG_MAC,
 711                            "mac bss changed beacon enabled %d\n",
 712                            bss_conf->enable_beacon);
 713
 714                if (bss_conf->enable_beacon) {
 715                        vif_priv->dtim_period = bss_conf->dtim_period;
 716                        vif_priv->bss_index = 0xff;
 717                        wcn36xx_smd_config_bss(wcn, vif, NULL,
 718                                               vif->addr, false);
 719                        skb = ieee80211_beacon_get_tim(hw, vif, &tim_off,
 720                                                       &tim_len);
 721                        if (!skb) {
 722                                wcn36xx_err("failed to alloc beacon skb\n");
 723                                goto out;
 724                        }
 725                        wcn36xx_smd_send_beacon(wcn, vif, skb, tim_off, 0);
 726                        dev_kfree_skb(skb);
 727
 728                        if (vif->type == NL80211_IFTYPE_ADHOC ||
 729                            vif->type == NL80211_IFTYPE_MESH_POINT)
 730                                link_state = WCN36XX_HAL_LINK_IBSS_STATE;
 731                        else
 732                                link_state = WCN36XX_HAL_LINK_AP_STATE;
 733
 734                        wcn36xx_smd_set_link_st(wcn, vif->addr, vif->addr,
 735                                                link_state);
 736                } else {
 737                        wcn36xx_smd_set_link_st(wcn, vif->addr, vif->addr,
 738                                                WCN36XX_HAL_LINK_IDLE_STATE);
 739                        wcn36xx_smd_delete_bss(wcn, vif);
 740                }
 741        }
 742out:
 743        return;
 744}
 745
 746/* this is required when using IEEE80211_HW_HAS_RATE_CONTROL */
 747static int wcn36xx_set_rts_threshold(struct ieee80211_hw *hw, u32 value)
 748{
 749        struct wcn36xx *wcn = hw->priv;
 750        wcn36xx_dbg(WCN36XX_DBG_MAC, "mac set RTS threshold %d\n", value);
 751
 752        wcn36xx_smd_update_cfg(wcn, WCN36XX_HAL_CFG_RTS_THRESHOLD, value);
 753        return 0;
 754}
 755
 756static void wcn36xx_remove_interface(struct ieee80211_hw *hw,
 757                                     struct ieee80211_vif *vif)
 758{
 759        struct wcn36xx *wcn = hw->priv;
 760        struct wcn36xx_vif *vif_priv = (struct wcn36xx_vif *)vif->drv_priv;
 761        wcn36xx_dbg(WCN36XX_DBG_MAC, "mac remove interface vif %p\n", vif);
 762
 763        list_del(&vif_priv->list);
 764        wcn36xx_smd_delete_sta_self(wcn, vif->addr);
 765}
 766
 767static int wcn36xx_add_interface(struct ieee80211_hw *hw,
 768                                 struct ieee80211_vif *vif)
 769{
 770        struct wcn36xx *wcn = hw->priv;
 771        struct wcn36xx_vif *vif_priv = (struct wcn36xx_vif *)vif->drv_priv;
 772
 773        wcn36xx_dbg(WCN36XX_DBG_MAC, "mac add interface vif %p type %d\n",
 774                    vif, vif->type);
 775
 776        if (!(NL80211_IFTYPE_STATION == vif->type ||
 777              NL80211_IFTYPE_AP == vif->type ||
 778              NL80211_IFTYPE_ADHOC == vif->type ||
 779              NL80211_IFTYPE_MESH_POINT == vif->type)) {
 780                wcn36xx_warn("Unsupported interface type requested: %d\n",
 781                             vif->type);
 782                return -EOPNOTSUPP;
 783        }
 784
 785        list_add(&vif_priv->list, &wcn->vif_list);
 786        wcn36xx_smd_add_sta_self(wcn, vif);
 787
 788        return 0;
 789}
 790
 791static int wcn36xx_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
 792                           struct ieee80211_sta *sta)
 793{
 794        struct wcn36xx *wcn = hw->priv;
 795        struct wcn36xx_vif *vif_priv = (struct wcn36xx_vif *)vif->drv_priv;
 796        struct wcn36xx_sta *sta_priv = (struct wcn36xx_sta *)sta->drv_priv;
 797        wcn36xx_dbg(WCN36XX_DBG_MAC, "mac sta add vif %p sta %pM\n",
 798                    vif, sta->addr);
 799
 800        spin_lock_init(&sta_priv->ampdu_lock);
 801        vif_priv->sta = sta_priv;
 802        sta_priv->vif = vif_priv;
 803        /*
 804         * For STA mode HW will be configured on BSS_CHANGED_ASSOC because
 805         * at this stage AID is not available yet.
 806         */
 807        if (NL80211_IFTYPE_STATION != vif->type) {
 808                wcn36xx_update_allowed_rates(sta, WCN36XX_BAND(wcn));
 809                sta_priv->aid = sta->aid;
 810                wcn36xx_smd_config_sta(wcn, vif, sta);
 811        }
 812        return 0;
 813}
 814
 815static int wcn36xx_sta_remove(struct ieee80211_hw *hw,
 816                              struct ieee80211_vif *vif,
 817                              struct ieee80211_sta *sta)
 818{
 819        struct wcn36xx *wcn = hw->priv;
 820        struct wcn36xx_vif *vif_priv = (struct wcn36xx_vif *)vif->drv_priv;
 821        struct wcn36xx_sta *sta_priv = (struct wcn36xx_sta *)sta->drv_priv;
 822
 823        wcn36xx_dbg(WCN36XX_DBG_MAC, "mac sta remove vif %p sta %pM index %d\n",
 824                    vif, sta->addr, sta_priv->sta_index);
 825
 826        wcn36xx_smd_delete_sta(wcn, sta_priv->sta_index);
 827        vif_priv->sta = NULL;
 828        sta_priv->vif = NULL;
 829        return 0;
 830}
 831
 832#ifdef CONFIG_PM
 833
 834static int wcn36xx_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wow)
 835{
 836        struct wcn36xx *wcn = hw->priv;
 837
 838        wcn36xx_dbg(WCN36XX_DBG_MAC, "mac suspend\n");
 839
 840        flush_workqueue(wcn->hal_ind_wq);
 841        wcn36xx_smd_set_power_params(wcn, true);
 842        return 0;
 843}
 844
 845static int wcn36xx_resume(struct ieee80211_hw *hw)
 846{
 847        struct wcn36xx *wcn = hw->priv;
 848
 849        wcn36xx_dbg(WCN36XX_DBG_MAC, "mac resume\n");
 850
 851        flush_workqueue(wcn->hal_ind_wq);
 852        wcn36xx_smd_set_power_params(wcn, false);
 853        return 0;
 854}
 855
 856#endif
 857
 858static int wcn36xx_ampdu_action(struct ieee80211_hw *hw,
 859                    struct ieee80211_vif *vif,
 860                    enum ieee80211_ampdu_mlme_action action,
 861                    struct ieee80211_sta *sta, u16 tid, u16 *ssn,
 862                    u8 buf_size)
 863{
 864        struct wcn36xx *wcn = hw->priv;
 865        struct wcn36xx_sta *sta_priv = NULL;
 866
 867        wcn36xx_dbg(WCN36XX_DBG_MAC, "mac ampdu action action %d tid %d\n",
 868                    action, tid);
 869
 870        sta_priv = (struct wcn36xx_sta *)sta->drv_priv;
 871
 872        switch (action) {
 873        case IEEE80211_AMPDU_RX_START:
 874                sta_priv->tid = tid;
 875                wcn36xx_smd_add_ba_session(wcn, sta, tid, ssn, 0,
 876                        get_sta_index(vif, sta_priv));
 877                wcn36xx_smd_add_ba(wcn);
 878                wcn36xx_smd_trigger_ba(wcn, get_sta_index(vif, sta_priv));
 879                break;
 880        case IEEE80211_AMPDU_RX_STOP:
 881                wcn36xx_smd_del_ba(wcn, tid, get_sta_index(vif, sta_priv));
 882                break;
 883        case IEEE80211_AMPDU_TX_START:
 884                spin_lock_bh(&sta_priv->ampdu_lock);
 885                sta_priv->ampdu_state[tid] = WCN36XX_AMPDU_START;
 886                spin_unlock_bh(&sta_priv->ampdu_lock);
 887
 888                ieee80211_start_tx_ba_cb_irqsafe(vif, sta->addr, tid);
 889                break;
 890        case IEEE80211_AMPDU_TX_OPERATIONAL:
 891                spin_lock_bh(&sta_priv->ampdu_lock);
 892                sta_priv->ampdu_state[tid] = WCN36XX_AMPDU_OPERATIONAL;
 893                spin_unlock_bh(&sta_priv->ampdu_lock);
 894
 895                wcn36xx_smd_add_ba_session(wcn, sta, tid, ssn, 1,
 896                        get_sta_index(vif, sta_priv));
 897                break;
 898        case IEEE80211_AMPDU_TX_STOP_FLUSH:
 899        case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT:
 900        case IEEE80211_AMPDU_TX_STOP_CONT:
 901                spin_lock_bh(&sta_priv->ampdu_lock);
 902                sta_priv->ampdu_state[tid] = WCN36XX_AMPDU_NONE;
 903                spin_unlock_bh(&sta_priv->ampdu_lock);
 904
 905                ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid);
 906                break;
 907        default:
 908                wcn36xx_err("Unknown AMPDU action\n");
 909        }
 910
 911        return 0;
 912}
 913
 914static const struct ieee80211_ops wcn36xx_ops = {
 915        .start                  = wcn36xx_start,
 916        .stop                   = wcn36xx_stop,
 917        .add_interface          = wcn36xx_add_interface,
 918        .remove_interface       = wcn36xx_remove_interface,
 919#ifdef CONFIG_PM
 920        .suspend                = wcn36xx_suspend,
 921        .resume                 = wcn36xx_resume,
 922#endif
 923        .config                 = wcn36xx_config,
 924        .configure_filter       = wcn36xx_configure_filter,
 925        .tx                     = wcn36xx_tx,
 926        .set_key                = wcn36xx_set_key,
 927        .sw_scan_start          = wcn36xx_sw_scan_start,
 928        .sw_scan_complete       = wcn36xx_sw_scan_complete,
 929        .bss_info_changed       = wcn36xx_bss_info_changed,
 930        .set_rts_threshold      = wcn36xx_set_rts_threshold,
 931        .sta_add                = wcn36xx_sta_add,
 932        .sta_remove             = wcn36xx_sta_remove,
 933        .ampdu_action           = wcn36xx_ampdu_action,
 934};
 935
 936static int wcn36xx_init_ieee80211(struct wcn36xx *wcn)
 937{
 938        int ret = 0;
 939
 940        static const u32 cipher_suites[] = {
 941                WLAN_CIPHER_SUITE_WEP40,
 942                WLAN_CIPHER_SUITE_WEP104,
 943                WLAN_CIPHER_SUITE_TKIP,
 944                WLAN_CIPHER_SUITE_CCMP,
 945        };
 946
 947        ieee80211_hw_set(wcn->hw, TIMING_BEACON_ONLY);
 948        ieee80211_hw_set(wcn->hw, AMPDU_AGGREGATION);
 949        ieee80211_hw_set(wcn->hw, CONNECTION_MONITOR);
 950        ieee80211_hw_set(wcn->hw, SUPPORTS_PS);
 951        ieee80211_hw_set(wcn->hw, SIGNAL_DBM);
 952        ieee80211_hw_set(wcn->hw, HAS_RATE_CONTROL);
 953
 954        wcn->hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) |
 955                BIT(NL80211_IFTYPE_AP) |
 956                BIT(NL80211_IFTYPE_ADHOC) |
 957                BIT(NL80211_IFTYPE_MESH_POINT);
 958
 959        wcn->hw->wiphy->bands[IEEE80211_BAND_2GHZ] = &wcn_band_2ghz;
 960        wcn->hw->wiphy->bands[IEEE80211_BAND_5GHZ] = &wcn_band_5ghz;
 961
 962        wcn->hw->wiphy->cipher_suites = cipher_suites;
 963        wcn->hw->wiphy->n_cipher_suites = ARRAY_SIZE(cipher_suites);
 964
 965        wcn->hw->wiphy->flags |= WIPHY_FLAG_AP_PROBE_RESP_OFFLOAD;
 966
 967#ifdef CONFIG_PM
 968        wcn->hw->wiphy->wowlan = &wowlan_support;
 969#endif
 970
 971        wcn->hw->max_listen_interval = 200;
 972
 973        wcn->hw->queues = 4;
 974
 975        SET_IEEE80211_DEV(wcn->hw, wcn->dev);
 976
 977        wcn->hw->sta_data_size = sizeof(struct wcn36xx_sta);
 978        wcn->hw->vif_data_size = sizeof(struct wcn36xx_vif);
 979
 980        return ret;
 981}
 982
 983static int wcn36xx_platform_get_resources(struct wcn36xx *wcn,
 984                                          struct platform_device *pdev)
 985{
 986        struct resource *res;
 987        /* Set TX IRQ */
 988        res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
 989                                           "wcnss_wlantx_irq");
 990        if (!res) {
 991                wcn36xx_err("failed to get tx_irq\n");
 992                return -ENOENT;
 993        }
 994        wcn->tx_irq = res->start;
 995
 996        /* Set RX IRQ */
 997        res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
 998                                           "wcnss_wlanrx_irq");
 999        if (!res) {
1000                wcn36xx_err("failed to get rx_irq\n");
1001                return -ENOENT;
1002        }
1003        wcn->rx_irq = res->start;
1004
1005        /* Map the memory */
1006        res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
1007                                                 "wcnss_mmio");
1008        if (!res) {
1009                wcn36xx_err("failed to get mmio\n");
1010                return -ENOENT;
1011        }
1012        wcn->mmio = ioremap(res->start, resource_size(res));
1013        if (!wcn->mmio) {
1014                wcn36xx_err("failed to map io memory\n");
1015                return -ENOMEM;
1016        }
1017        return 0;
1018}
1019
1020static int wcn36xx_probe(struct platform_device *pdev)
1021{
1022        struct ieee80211_hw *hw;
1023        struct wcn36xx *wcn;
1024        int ret;
1025        u8 addr[ETH_ALEN];
1026
1027        wcn36xx_dbg(WCN36XX_DBG_MAC, "platform probe\n");
1028
1029        hw = ieee80211_alloc_hw(sizeof(struct wcn36xx), &wcn36xx_ops);
1030        if (!hw) {
1031                wcn36xx_err("failed to alloc hw\n");
1032                ret = -ENOMEM;
1033                goto out_err;
1034        }
1035        platform_set_drvdata(pdev, hw);
1036        wcn = hw->priv;
1037        wcn->hw = hw;
1038        wcn->dev = &pdev->dev;
1039        wcn->ctrl_ops = pdev->dev.platform_data;
1040
1041        mutex_init(&wcn->hal_mutex);
1042
1043        if (!wcn->ctrl_ops->get_hw_mac(addr)) {
1044                wcn36xx_info("mac address: %pM\n", addr);
1045                SET_IEEE80211_PERM_ADDR(wcn->hw, addr);
1046        }
1047
1048        ret = wcn36xx_platform_get_resources(wcn, pdev);
1049        if (ret)
1050                goto out_wq;
1051
1052        wcn36xx_init_ieee80211(wcn);
1053        ret = ieee80211_register_hw(wcn->hw);
1054        if (ret)
1055                goto out_unmap;
1056
1057        return 0;
1058
1059out_unmap:
1060        iounmap(wcn->mmio);
1061out_wq:
1062        ieee80211_free_hw(hw);
1063out_err:
1064        return ret;
1065}
1066static int wcn36xx_remove(struct platform_device *pdev)
1067{
1068        struct ieee80211_hw *hw = platform_get_drvdata(pdev);
1069        struct wcn36xx *wcn = hw->priv;
1070        wcn36xx_dbg(WCN36XX_DBG_MAC, "platform remove\n");
1071
1072        release_firmware(wcn->nv);
1073        mutex_destroy(&wcn->hal_mutex);
1074
1075        ieee80211_unregister_hw(hw);
1076        iounmap(wcn->mmio);
1077        ieee80211_free_hw(hw);
1078
1079        return 0;
1080}
1081static const struct platform_device_id wcn36xx_platform_id_table[] = {
1082        {
1083                .name = "wcn36xx",
1084                .driver_data = 0
1085        },
1086        {}
1087};
1088MODULE_DEVICE_TABLE(platform, wcn36xx_platform_id_table);
1089
1090static struct platform_driver wcn36xx_driver = {
1091        .probe      = wcn36xx_probe,
1092        .remove     = wcn36xx_remove,
1093        .driver         = {
1094                .name   = "wcn36xx",
1095        },
1096        .id_table    = wcn36xx_platform_id_table,
1097};
1098
1099static int __init wcn36xx_init(void)
1100{
1101        platform_driver_register(&wcn36xx_driver);
1102        return 0;
1103}
1104module_init(wcn36xx_init);
1105
1106static void __exit wcn36xx_exit(void)
1107{
1108        platform_driver_unregister(&wcn36xx_driver);
1109}
1110module_exit(wcn36xx_exit);
1111
1112MODULE_LICENSE("Dual BSD/GPL");
1113MODULE_AUTHOR("Eugene Krasnikov k.eugene.e@gmail.com");
1114MODULE_FIRMWARE(WLAN_NV_FILE);
1115